diff options
-rw-r--r-- | crates/hir_def/src/attr.rs | 105 | ||||
-rw-r--r-- | crates/ide/src/goto_definition.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 60 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 48 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_db/src/call_info.rs | 6 | ||||
-rw-r--r-- | crates/ide_db/src/call_info/tests.rs | 82 | ||||
-rw-r--r-- | crates/syntax/src/ast.rs | 29 | ||||
-rw-r--r-- | crates/syntax/src/ast/token_ext.rs | 19 | ||||
-rw-r--r-- | crates/syntax/src/ast/traits.rs | 16 |
10 files changed, 235 insertions, 140 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index 7ba53ee5c..b7353d868 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs | |||
@@ -76,37 +76,23 @@ impl ops::Deref for Attrs { | |||
76 | impl RawAttrs { | 76 | impl RawAttrs { |
77 | pub(crate) const EMPTY: Self = Self { entries: None }; | 77 | pub(crate) const EMPTY: Self = Self { entries: None }; |
78 | 78 | ||
79 | pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Self { | 79 | pub(crate) fn new(owner: &dyn ast::AttrsOwner, hygiene: &Hygiene) -> Self { |
80 | let attrs: Vec<_> = collect_attrs(owner).collect(); | 80 | let entries = collect_attrs(owner) |
81 | let entries = if attrs.is_empty() { | 81 | .enumerate() |
82 | // Avoid heap allocation | 82 | .flat_map(|(i, attr)| match attr { |
83 | None | 83 | Either::Left(attr) => Attr::from_src(attr, hygiene, i as u32), |
84 | } else { | 84 | Either::Right(comment) => comment.doc_comment().map(|doc| Attr { |
85 | Some( | 85 | index: i as u32, |
86 | attrs | 86 | input: Some(AttrInput::Literal(SmolStr::new(doc))), |
87 | .into_iter() | 87 | path: ModPath::from(hir_expand::name!(doc)), |
88 | .enumerate() | 88 | }), |
89 | .flat_map(|(i, attr)| match attr { | 89 | }) |
90 | Either::Left(attr) => Attr::from_src(attr, hygiene).map(|attr| (i, attr)), | 90 | .collect::<Arc<_>>(); |
91 | Either::Right(comment) => comment.doc_comment().map(|doc| { | 91 | |
92 | ( | 92 | Self { entries: if entries.is_empty() { None } else { Some(entries) } } |
93 | i, | ||
94 | Attr { | ||
95 | index: 0, | ||
96 | input: Some(AttrInput::Literal(SmolStr::new(doc))), | ||
97 | path: ModPath::from(hir_expand::name!(doc)), | ||
98 | }, | ||
99 | ) | ||
100 | }), | ||
101 | }) | ||
102 | .map(|(i, attr)| Attr { index: i as u32, ..attr }) | ||
103 | .collect(), | ||
104 | ) | ||
105 | }; | ||
106 | Self { entries } | ||
107 | } | 93 | } |
108 | 94 | ||
109 | fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Self { | 95 | fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::AttrsOwner>) -> Self { |
110 | let hygiene = Hygiene::new(db.upcast(), owner.file_id); | 96 | let hygiene = Hygiene::new(db.upcast(), owner.file_id); |
111 | Self::new(owner.value, &hygiene) | 97 | Self::new(owner.value, &hygiene) |
112 | } | 98 | } |
@@ -162,7 +148,7 @@ impl RawAttrs { | |||
162 | let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?; | 148 | let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?; |
163 | // FIXME hygiene | 149 | // FIXME hygiene |
164 | let hygiene = Hygiene::new_unhygienic(); | 150 | let hygiene = Hygiene::new_unhygienic(); |
165 | Attr::from_src(attr, &hygiene).map(|attr| Attr { index, ..attr }) | 151 | Attr::from_src(attr, &hygiene, index) |
166 | }); | 152 | }); |
167 | 153 | ||
168 | let cfg_options = &crate_graph[krate].cfg_options; | 154 | let cfg_options = &crate_graph[krate].cfg_options; |
@@ -192,7 +178,7 @@ impl Attrs { | |||
192 | Some(it) => { | 178 | Some(it) => { |
193 | let raw_attrs = RawAttrs::from_attrs_owner( | 179 | let raw_attrs = RawAttrs::from_attrs_owner( |
194 | db, | 180 | db, |
195 | it.as_ref().map(|it| it as &dyn AttrsOwner), | 181 | it.as_ref().map(|it| it as &dyn ast::AttrsOwner), |
196 | ); | 182 | ); |
197 | match mod_data.definition_source(db) { | 183 | match mod_data.definition_source(db) { |
198 | InFile { file_id, value: ModuleSource::SourceFile(file) } => raw_attrs | 184 | InFile { file_id, value: ModuleSource::SourceFile(file) } => raw_attrs |
@@ -203,9 +189,9 @@ impl Attrs { | |||
203 | None => RawAttrs::from_attrs_owner( | 189 | None => RawAttrs::from_attrs_owner( |
204 | db, | 190 | db, |
205 | mod_data.definition_source(db).as_ref().map(|src| match src { | 191 | mod_data.definition_source(db).as_ref().map(|src| match src { |
206 | ModuleSource::SourceFile(file) => file as &dyn AttrsOwner, | 192 | ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner, |
207 | ModuleSource::Module(module) => module as &dyn AttrsOwner, | 193 | ModuleSource::Module(module) => module as &dyn ast::AttrsOwner, |
208 | ModuleSource::BlockExpr(block) => block as &dyn AttrsOwner, | 194 | ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner, |
209 | }), | 195 | }), |
210 | ), | 196 | ), |
211 | } | 197 | } |
@@ -263,7 +249,7 @@ impl Attrs { | |||
263 | let mut res = ArenaMap::default(); | 249 | let mut res = ArenaMap::default(); |
264 | 250 | ||
265 | for (id, var) in src.value.iter() { | 251 | for (id, var) in src.value.iter() { |
266 | let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn AttrsOwner)) | 252 | let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn ast::AttrsOwner)) |
267 | .filter(db, krate); | 253 | .filter(db, krate); |
268 | 254 | ||
269 | res.insert(id, attrs) | 255 | res.insert(id, attrs) |
@@ -297,7 +283,7 @@ impl Attrs { | |||
297 | /// Constructs a map that maps the lowered `Attr`s in this `Attrs` back to its original syntax nodes. | 283 | /// Constructs a map that maps the lowered `Attr`s in this `Attrs` back to its original syntax nodes. |
298 | /// | 284 | /// |
299 | /// `owner` must be the original owner of the attributes. | 285 | /// `owner` must be the original owner of the attributes. |
300 | pub fn source_map(&self, owner: &dyn AttrsOwner) -> AttrSourceMap { | 286 | pub fn source_map(&self, owner: &dyn ast::AttrsOwner) -> AttrSourceMap { |
301 | AttrSourceMap { attrs: collect_attrs(owner).collect() } | 287 | AttrSourceMap { attrs: collect_attrs(owner).collect() } |
302 | } | 288 | } |
303 | 289 | ||
@@ -325,15 +311,34 @@ impl Attrs { | |||
325 | AttrInput::Literal(s) => Some(s), | 311 | AttrInput::Literal(s) => Some(s), |
326 | AttrInput::TokenTree(_) => None, | 312 | AttrInput::TokenTree(_) => None, |
327 | }); | 313 | }); |
328 | // FIXME: Replace `Itertools::intersperse` with `Iterator::intersperse[_with]` until the | 314 | let indent = docs |
329 | // libstd api gets stabilized (https://github.com/rust-lang/rust/issues/79524). | 315 | .clone() |
330 | let docs = Itertools::intersperse(docs, &SmolStr::new_inline("\n")) | 316 | .flat_map(|s| s.lines()) |
331 | .map(|it| it.as_str()) | 317 | .filter(|line| !line.chars().all(|c| c.is_whitespace())) |
332 | .collect::<String>(); | 318 | .map(|line| line.chars().take_while(|c| c.is_whitespace()).count()) |
333 | if docs.is_empty() { | 319 | .min() |
320 | .unwrap_or(0); | ||
321 | let mut buf = String::new(); | ||
322 | for doc in docs { | ||
323 | // str::lines doesn't yield anything for the empty string | ||
324 | if !doc.is_empty() { | ||
325 | buf.extend(Itertools::intersperse( | ||
326 | doc.lines().map(|line| { | ||
327 | line.char_indices() | ||
328 | .nth(indent) | ||
329 | .map_or(line, |(offset, _)| &line[offset..]) | ||
330 | .trim_end() | ||
331 | }), | ||
332 | "\n", | ||
333 | )); | ||
334 | } | ||
335 | buf.push('\n'); | ||
336 | } | ||
337 | buf.pop(); | ||
338 | if buf.is_empty() { | ||
334 | None | 339 | None |
335 | } else { | 340 | } else { |
336 | Some(Documentation(docs)) | 341 | Some(Documentation(buf)) |
337 | } | 342 | } |
338 | } | 343 | } |
339 | } | 344 | } |
@@ -407,7 +412,7 @@ pub enum AttrInput { | |||
407 | } | 412 | } |
408 | 413 | ||
409 | impl Attr { | 414 | impl Attr { |
410 | fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> { | 415 | fn from_src(ast: ast::Attr, hygiene: &Hygiene, index: u32) -> Option<Attr> { |
411 | let path = ModPath::from_src(ast.path()?, hygiene)?; | 416 | let path = ModPath::from_src(ast.path()?, hygiene)?; |
412 | let input = if let Some(lit) = ast.literal() { | 417 | let input = if let Some(lit) = ast.literal() { |
413 | let value = match lit.kind() { | 418 | let value = match lit.kind() { |
@@ -420,7 +425,7 @@ impl Attr { | |||
420 | } else { | 425 | } else { |
421 | None | 426 | None |
422 | }; | 427 | }; |
423 | Some(Attr { index: 0, path, input }) | 428 | Some(Attr { index, path, input }) |
424 | } | 429 | } |
425 | 430 | ||
426 | /// Maps this lowered `Attr` back to its original syntax node. | 431 | /// Maps this lowered `Attr` back to its original syntax node. |
@@ -429,7 +434,7 @@ impl Attr { | |||
429 | /// | 434 | /// |
430 | /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of | 435 | /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of |
431 | /// the attribute represented by `Attr`. | 436 | /// the attribute represented by `Attr`. |
432 | pub fn to_src(&self, owner: &dyn AttrsOwner) -> Either<ast::Attr, ast::Comment> { | 437 | pub fn to_src(&self, owner: &dyn ast::AttrsOwner) -> Either<ast::Attr, ast::Comment> { |
433 | collect_attrs(owner).nth(self.index as usize).unwrap_or_else(|| { | 438 | collect_attrs(owner).nth(self.index as usize).unwrap_or_else(|| { |
434 | panic!("cannot find `Attr` at index {} in {}", self.index, owner.syntax()) | 439 | panic!("cannot find `Attr` at index {} in {}", self.index, owner.syntax()) |
435 | }) | 440 | }) |
@@ -508,7 +513,7 @@ impl<'a> AttrQuery<'a> { | |||
508 | self.attrs().next().is_some() | 513 | self.attrs().next().is_some() |
509 | } | 514 | } |
510 | 515 | ||
511 | pub fn attrs(self) -> impl Iterator<Item = &'a Attr> { | 516 | pub fn attrs(self) -> impl Iterator<Item = &'a Attr> + Clone { |
512 | let key = self.key; | 517 | let key = self.key; |
513 | self.attrs | 518 | self.attrs |
514 | .iter() | 519 | .iter() |
@@ -521,7 +526,7 @@ where | |||
521 | N: ast::AttrsOwner, | 526 | N: ast::AttrsOwner, |
522 | { | 527 | { |
523 | let src = InFile::new(src.file_id, src.to_node(db.upcast())); | 528 | let src = InFile::new(src.file_id, src.to_node(db.upcast())); |
524 | RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner)) | 529 | RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn ast::AttrsOwner)) |
525 | } | 530 | } |
526 | 531 | ||
527 | fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs { | 532 | fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs { |
@@ -530,7 +535,9 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase | |||
530 | tree.raw_attrs(mod_item.into()).clone() | 535 | tree.raw_attrs(mod_item.into()).clone() |
531 | } | 536 | } |
532 | 537 | ||
533 | fn collect_attrs(owner: &dyn AttrsOwner) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> { | 538 | fn collect_attrs( |
539 | owner: &dyn ast::AttrsOwner, | ||
540 | ) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> { | ||
534 | let (inner_attrs, inner_docs) = inner_attributes(owner.syntax()) | 541 | let (inner_attrs, inner_docs) = inner_attributes(owner.syntax()) |
535 | .map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs)))); | 542 | .map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs)))); |
536 | 543 | ||
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index b71f4917c..5072ecea0 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -95,12 +95,10 @@ fn extract_positioned_link_from_comment( | |||
95 | let comment_range = comment.syntax().text_range(); | 95 | let comment_range = comment.syntax().text_range(); |
96 | let doc_comment = comment.doc_comment()?; | 96 | let doc_comment = comment.doc_comment()?; |
97 | let def_links = extract_definitions_from_markdown(doc_comment); | 97 | let def_links = extract_definitions_from_markdown(doc_comment); |
98 | let start = comment_range.start() + TextSize::from(comment.prefix().len() as u32); | ||
98 | let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| { | 99 | let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| { |
99 | let matched_position = comment_range.start() + TextSize::from(def_link_range.start as u32); | 100 | let matched_position = start + TextSize::from(def_link_range.start as u32); |
100 | match position.offset.checked_sub(matched_position) { | 101 | position.offset.checked_sub(matched_position).unwrap_or_else(|| comment_range.end()) |
101 | Some(distance) => distance, | ||
102 | None => comment_range.end(), | ||
103 | } | ||
104 | })?; | 102 | })?; |
105 | Some((def_link.to_string(), *ns)) | 103 | Some((def_link.to_string(), *ns)) |
106 | } | 104 | } |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 325014622..15d309d7d 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -1533,12 +1533,21 @@ fn my() {} | |||
1533 | fn test_hover_struct_doc_comment() { | 1533 | fn test_hover_struct_doc_comment() { |
1534 | check( | 1534 | check( |
1535 | r#" | 1535 | r#" |
1536 | /// bar docs | 1536 | /// This is an example |
1537 | /// multiline doc | ||
1538 | /// | ||
1539 | /// # Example | ||
1540 | /// | ||
1541 | /// ``` | ||
1542 | /// let five = 5; | ||
1543 | /// | ||
1544 | /// assert_eq!(6, my_crate::add_one(5)); | ||
1545 | /// ``` | ||
1537 | struct Bar; | 1546 | struct Bar; |
1538 | 1547 | ||
1539 | fn foo() { let bar = Ba$0r; } | 1548 | fn foo() { let bar = Ba$0r; } |
1540 | "#, | 1549 | "#, |
1541 | expect![[r#" | 1550 | expect![[r##" |
1542 | *Bar* | 1551 | *Bar* |
1543 | 1552 | ||
1544 | ```rust | 1553 | ```rust |
@@ -1551,8 +1560,17 @@ fn foo() { let bar = Ba$0r; } | |||
1551 | 1560 | ||
1552 | --- | 1561 | --- |
1553 | 1562 | ||
1554 | bar docs | 1563 | This is an example |
1555 | "#]], | 1564 | multiline doc |
1565 | |||
1566 | # Example | ||
1567 | |||
1568 | ``` | ||
1569 | let five = 5; | ||
1570 | |||
1571 | assert_eq!(6, my_crate::add_one(5)); | ||
1572 | ``` | ||
1573 | "##]], | ||
1556 | ); | 1574 | ); |
1557 | } | 1575 | } |
1558 | 1576 | ||
@@ -3424,6 +3442,40 @@ mod Foo$0 { | |||
3424 | } | 3442 | } |
3425 | 3443 | ||
3426 | #[test] | 3444 | #[test] |
3445 | fn hover_doc_block_style_indentend() { | ||
3446 | check( | ||
3447 | r#" | ||
3448 | /** | ||
3449 | foo | ||
3450 | ```rust | ||
3451 | let x = 3; | ||
3452 | ``` | ||
3453 | */ | ||
3454 | fn foo$0() {} | ||
3455 | "#, | ||
3456 | expect![[r#" | ||
3457 | *foo* | ||
3458 | |||
3459 | ```rust | ||
3460 | test | ||
3461 | ``` | ||
3462 | |||
3463 | ```rust | ||
3464 | fn foo() | ||
3465 | ``` | ||
3466 | |||
3467 | --- | ||
3468 | |||
3469 | foo | ||
3470 | |||
3471 | ```rust | ||
3472 | let x = 3; | ||
3473 | ``` | ||
3474 | "#]], | ||
3475 | ); | ||
3476 | } | ||
3477 | |||
3478 | #[test] | ||
3427 | fn hover_comments_dont_highlight_parent() { | 3479 | fn hover_comments_dont_highlight_parent() { |
3428 | check_hover_no_result( | 3480 | check_hover_no_result( |
3429 | r#" | 3481 | r#" |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 397e2126b..bea020b06 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -576,6 +576,20 @@ fn should_have_runnable_1() {} | |||
576 | /// ``` | 576 | /// ``` |
577 | fn should_have_runnable_2() {} | 577 | fn should_have_runnable_2() {} |
578 | 578 | ||
579 | /** | ||
580 | ```rust | ||
581 | let z = 55; | ||
582 | ``` | ||
583 | */ | ||
584 | fn should_have_no_runnable_3() {} | ||
585 | |||
586 | /** | ||
587 | ```rust | ||
588 | let z = 55; | ||
589 | ``` | ||
590 | */ | ||
591 | fn should_have_no_runnable_4() {} | ||
592 | |||
579 | /// ```no_run | 593 | /// ```no_run |
580 | /// let z = 55; | 594 | /// let z = 55; |
581 | /// ``` | 595 | /// ``` |
@@ -616,7 +630,7 @@ fn should_have_no_runnable_6() {} | |||
616 | struct StructWithRunnable(String); | 630 | struct StructWithRunnable(String); |
617 | 631 | ||
618 | "#, | 632 | "#, |
619 | &[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST], | 633 | &[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST], |
620 | expect![[r#" | 634 | expect![[r#" |
621 | [ | 635 | [ |
622 | Runnable { | 636 | Runnable { |
@@ -682,7 +696,37 @@ struct StructWithRunnable(String); | |||
682 | file_id: FileId( | 696 | file_id: FileId( |
683 | 0, | 697 | 0, |
684 | ), | 698 | ), |
685 | full_range: 756..821, | 699 | full_range: 256..320, |
700 | name: "should_have_no_runnable_3", | ||
701 | }, | ||
702 | kind: DocTest { | ||
703 | test_id: Path( | ||
704 | "should_have_no_runnable_3", | ||
705 | ), | ||
706 | }, | ||
707 | cfg: None, | ||
708 | }, | ||
709 | Runnable { | ||
710 | nav: NavigationTarget { | ||
711 | file_id: FileId( | ||
712 | 0, | ||
713 | ), | ||
714 | full_range: 322..398, | ||
715 | name: "should_have_no_runnable_4", | ||
716 | }, | ||
717 | kind: DocTest { | ||
718 | test_id: Path( | ||
719 | "should_have_no_runnable_4", | ||
720 | ), | ||
721 | }, | ||
722 | cfg: None, | ||
723 | }, | ||
724 | Runnable { | ||
725 | nav: NavigationTarget { | ||
726 | file_id: FileId( | ||
727 | 0, | ||
728 | ), | ||
729 | full_range: 900..965, | ||
686 | name: "StructWithRunnable", | 730 | name: "StructWithRunnable", |
687 | }, | 731 | }, |
688 | kind: DocTest { | 732 | kind: DocTest { |
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 5b7ad38d5..d9ea7b7ea 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -255,7 +255,7 @@ fn foo() { | |||
255 | bar.fo$0; | 255 | bar.fo$0; |
256 | } | 256 | } |
257 | "#, | 257 | "#, |
258 | DetailAndDocumentation { detail: "fn(&self)", documentation: " Do the foo" }, | 258 | DetailAndDocumentation { detail: "fn(&self)", documentation: "Do the foo" }, |
259 | ); | 259 | ); |
260 | } | 260 | } |
261 | 261 | ||
diff --git a/crates/ide_db/src/call_info.rs b/crates/ide_db/src/call_info.rs index d4016973c..7e26c3ccf 100644 --- a/crates/ide_db/src/call_info.rs +++ b/crates/ide_db/src/call_info.rs | |||
@@ -53,15 +53,15 @@ pub fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> | |||
53 | 53 | ||
54 | match callable.kind() { | 54 | match callable.kind() { |
55 | hir::CallableKind::Function(func) => { | 55 | hir::CallableKind::Function(func) => { |
56 | res.doc = func.docs(db).map(|it| it.as_str().to_string()); | 56 | res.doc = func.docs(db).map(|it| it.into()); |
57 | format_to!(res.signature, "fn {}", func.name(db)); | 57 | format_to!(res.signature, "fn {}", func.name(db)); |
58 | } | 58 | } |
59 | hir::CallableKind::TupleStruct(strukt) => { | 59 | hir::CallableKind::TupleStruct(strukt) => { |
60 | res.doc = strukt.docs(db).map(|it| it.as_str().to_string()); | 60 | res.doc = strukt.docs(db).map(|it| it.into()); |
61 | format_to!(res.signature, "struct {}", strukt.name(db)); | 61 | format_to!(res.signature, "struct {}", strukt.name(db)); |
62 | } | 62 | } |
63 | hir::CallableKind::TupleEnumVariant(variant) => { | 63 | hir::CallableKind::TupleEnumVariant(variant) => { |
64 | res.doc = variant.docs(db).map(|it| it.as_str().to_string()); | 64 | res.doc = variant.docs(db).map(|it| it.into()); |
65 | format_to!( | 65 | format_to!( |
66 | res.signature, | 66 | res.signature, |
67 | "enum {}::{}", | 67 | "enum {}::{}", |
diff --git a/crates/ide_db/src/call_info/tests.rs b/crates/ide_db/src/call_info/tests.rs index 9f84c253c..75ab3eb6e 100644 --- a/crates/ide_db/src/call_info/tests.rs +++ b/crates/ide_db/src/call_info/tests.rs | |||
@@ -220,11 +220,11 @@ fn bar() { | |||
220 | } | 220 | } |
221 | "#, | 221 | "#, |
222 | expect![[r#" | 222 | expect![[r#" |
223 | test | 223 | test |
224 | ------ | 224 | ------ |
225 | fn foo(j: u32) -> u32 | 225 | fn foo(j: u32) -> u32 |
226 | (<j: u32>) | 226 | (<j: u32>) |
227 | "#]], | 227 | "#]], |
228 | ); | 228 | ); |
229 | } | 229 | } |
230 | 230 | ||
@@ -249,19 +249,19 @@ pub fn do() { | |||
249 | add_one($0 | 249 | add_one($0 |
250 | }"#, | 250 | }"#, |
251 | expect![[r##" | 251 | expect![[r##" |
252 | Adds one to the number given. | 252 | Adds one to the number given. |
253 | 253 | ||
254 | # Examples | 254 | # Examples |
255 | 255 | ||
256 | ``` | 256 | ``` |
257 | let five = 5; | 257 | let five = 5; |
258 | 258 | ||
259 | assert_eq!(6, my_crate::add_one(5)); | 259 | assert_eq!(6, my_crate::add_one(5)); |
260 | ``` | 260 | ``` |
261 | ------ | 261 | ------ |
262 | fn add_one(x: i32) -> i32 | 262 | fn add_one(x: i32) -> i32 |
263 | (<x: i32>) | 263 | (<x: i32>) |
264 | "##]], | 264 | "##]], |
265 | ); | 265 | ); |
266 | } | 266 | } |
267 | 267 | ||
@@ -291,19 +291,19 @@ pub fn do_it() { | |||
291 | } | 291 | } |
292 | "#, | 292 | "#, |
293 | expect![[r##" | 293 | expect![[r##" |
294 | Adds one to the number given. | 294 | Adds one to the number given. |
295 | 295 | ||
296 | # Examples | 296 | # Examples |
297 | 297 | ||
298 | ``` | 298 | ``` |
299 | let five = 5; | 299 | let five = 5; |
300 | 300 | ||
301 | assert_eq!(6, my_crate::add_one(5)); | 301 | assert_eq!(6, my_crate::add_one(5)); |
302 | ``` | 302 | ``` |
303 | ------ | 303 | ------ |
304 | fn add_one(x: i32) -> i32 | 304 | fn add_one(x: i32) -> i32 |
305 | (<x: i32>) | 305 | (<x: i32>) |
306 | "##]], | 306 | "##]], |
307 | ); | 307 | ); |
308 | } | 308 | } |
309 | 309 | ||
@@ -335,13 +335,13 @@ pub fn foo(mut r: WriteHandler<()>) { | |||
335 | } | 335 | } |
336 | "#, | 336 | "#, |
337 | expect![[r#" | 337 | expect![[r#" |
338 | Method is called when writer finishes. | 338 | Method is called when writer finishes. |
339 | 339 | ||
340 | By default this method stops actor's `Context`. | 340 | By default this method stops actor's `Context`. |
341 | ------ | 341 | ------ |
342 | fn finished(&mut self, ctx: &mut {unknown}) | 342 | fn finished(&mut self, ctx: &mut {unknown}) |
343 | (<ctx: &mut {unknown}>) | 343 | (<ctx: &mut {unknown}>) |
344 | "#]], | 344 | "#]], |
345 | ); | 345 | ); |
346 | } | 346 | } |
347 | 347 | ||
@@ -389,11 +389,11 @@ fn main() { | |||
389 | } | 389 | } |
390 | "#, | 390 | "#, |
391 | expect![[r#" | 391 | expect![[r#" |
392 | A cool tuple struct | 392 | A cool tuple struct |
393 | ------ | 393 | ------ |
394 | struct S(u32, i32) | 394 | struct S(u32, i32) |
395 | (u32, <i32>) | 395 | (u32, <i32>) |
396 | "#]], | 396 | "#]], |
397 | ); | 397 | ); |
398 | } | 398 | } |
399 | 399 | ||
@@ -431,11 +431,11 @@ fn main() { | |||
431 | } | 431 | } |
432 | "#, | 432 | "#, |
433 | expect![[r#" | 433 | expect![[r#" |
434 | A Variant | 434 | A Variant |
435 | ------ | 435 | ------ |
436 | enum E::A(i32) | 436 | enum E::A(i32) |
437 | (<i32>) | 437 | (<i32>) |
438 | "#]], | 438 | "#]], |
439 | ); | 439 | ); |
440 | } | 440 | } |
441 | 441 | ||
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index 19261686c..38e0b04ef 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs | |||
@@ -118,7 +118,7 @@ fn test_doc_comment_none() { | |||
118 | .ok() | 118 | .ok() |
119 | .unwrap(); | 119 | .unwrap(); |
120 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 120 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
121 | assert!(module.doc_comment_text().is_none()); | 121 | assert!(module.doc_comments().doc_comment_text().is_none()); |
122 | } | 122 | } |
123 | 123 | ||
124 | #[test] | 124 | #[test] |
@@ -133,7 +133,7 @@ fn test_outer_doc_comment_of_items() { | |||
133 | .ok() | 133 | .ok() |
134 | .unwrap(); | 134 | .unwrap(); |
135 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 135 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
136 | assert_eq!("doc", module.doc_comment_text().unwrap()); | 136 | assert_eq!(" doc", module.doc_comments().doc_comment_text().unwrap()); |
137 | } | 137 | } |
138 | 138 | ||
139 | #[test] | 139 | #[test] |
@@ -148,7 +148,7 @@ fn test_inner_doc_comment_of_items() { | |||
148 | .ok() | 148 | .ok() |
149 | .unwrap(); | 149 | .unwrap(); |
150 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 150 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
151 | assert!(module.doc_comment_text().is_none()); | 151 | assert!(module.doc_comments().doc_comment_text().is_none()); |
152 | } | 152 | } |
153 | 153 | ||
154 | #[test] | 154 | #[test] |
@@ -162,7 +162,7 @@ fn test_doc_comment_of_statics() { | |||
162 | .ok() | 162 | .ok() |
163 | .unwrap(); | 163 | .unwrap(); |
164 | let st = file.syntax().descendants().find_map(Static::cast).unwrap(); | 164 | let st = file.syntax().descendants().find_map(Static::cast).unwrap(); |
165 | assert_eq!("Number of levels", st.doc_comment_text().unwrap()); | 165 | assert_eq!(" Number of levels", st.doc_comments().doc_comment_text().unwrap()); |
166 | } | 166 | } |
167 | 167 | ||
168 | #[test] | 168 | #[test] |
@@ -181,7 +181,10 @@ fn test_doc_comment_preserves_indents() { | |||
181 | .ok() | 181 | .ok() |
182 | .unwrap(); | 182 | .unwrap(); |
183 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 183 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
184 | assert_eq!("doc1\n```\nfn foo() {\n // ...\n}\n```", module.doc_comment_text().unwrap()); | 184 | assert_eq!( |
185 | " doc1\n ```\n fn foo() {\n // ...\n }\n ```", | ||
186 | module.doc_comments().doc_comment_text().unwrap() | ||
187 | ); | ||
185 | } | 188 | } |
186 | 189 | ||
187 | #[test] | 190 | #[test] |
@@ -198,7 +201,7 @@ fn test_doc_comment_preserves_newlines() { | |||
198 | .ok() | 201 | .ok() |
199 | .unwrap(); | 202 | .unwrap(); |
200 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 203 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
201 | assert_eq!("this\nis\nmod\nfoo", module.doc_comment_text().unwrap()); | 204 | assert_eq!(" this\n is\n mod\n foo", module.doc_comments().doc_comment_text().unwrap()); |
202 | } | 205 | } |
203 | 206 | ||
204 | #[test] | 207 | #[test] |
@@ -212,7 +215,7 @@ fn test_doc_comment_single_line_block_strips_suffix() { | |||
212 | .ok() | 215 | .ok() |
213 | .unwrap(); | 216 | .unwrap(); |
214 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 217 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
215 | assert_eq!("this is mod foo", module.doc_comment_text().unwrap()); | 218 | assert_eq!(" this is mod foo", module.doc_comments().doc_comment_text().unwrap()); |
216 | } | 219 | } |
217 | 220 | ||
218 | #[test] | 221 | #[test] |
@@ -226,7 +229,7 @@ fn test_doc_comment_single_line_block_strips_suffix_whitespace() { | |||
226 | .ok() | 229 | .ok() |
227 | .unwrap(); | 230 | .unwrap(); |
228 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 231 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
229 | assert_eq!("this is mod foo ", module.doc_comment_text().unwrap()); | 232 | assert_eq!(" this is mod foo ", module.doc_comments().doc_comment_text().unwrap()); |
230 | } | 233 | } |
231 | 234 | ||
232 | #[test] | 235 | #[test] |
@@ -245,8 +248,8 @@ fn test_doc_comment_multi_line_block_strips_suffix() { | |||
245 | .unwrap(); | 248 | .unwrap(); |
246 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 249 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
247 | assert_eq!( | 250 | assert_eq!( |
248 | " this\n is\n mod foo\n ", | 251 | "\n this\n is\n mod foo\n ", |
249 | module.doc_comment_text().unwrap() | 252 | module.doc_comments().doc_comment_text().unwrap() |
250 | ); | 253 | ); |
251 | } | 254 | } |
252 | 255 | ||
@@ -259,8 +262,8 @@ fn test_comments_preserve_trailing_whitespace() { | |||
259 | .unwrap(); | 262 | .unwrap(); |
260 | let def = file.syntax().descendants().find_map(Struct::cast).unwrap(); | 263 | let def = file.syntax().descendants().find_map(Struct::cast).unwrap(); |
261 | assert_eq!( | 264 | assert_eq!( |
262 | "Representation of a Realm. \nIn the specification these are called Realm Records.", | 265 | " Representation of a Realm. \n In the specification these are called Realm Records.", |
263 | def.doc_comment_text().unwrap() | 266 | def.doc_comments().doc_comment_text().unwrap() |
264 | ); | 267 | ); |
265 | } | 268 | } |
266 | 269 | ||
@@ -276,7 +279,7 @@ fn test_four_slash_line_comment() { | |||
276 | .ok() | 279 | .ok() |
277 | .unwrap(); | 280 | .unwrap(); |
278 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | 281 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); |
279 | assert_eq!("doc comment", module.doc_comment_text().unwrap()); | 282 | assert_eq!(" doc comment", module.doc_comments().doc_comment_text().unwrap()); |
280 | } | 283 | } |
281 | 284 | ||
282 | #[test] | 285 | #[test] |
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs index 977eb8181..6c242d126 100644 --- a/crates/syntax/src/ast/token_ext.rs +++ b/crates/syntax/src/ast/token_ext.rs | |||
@@ -33,23 +33,20 @@ impl ast::Comment { | |||
33 | prefix | 33 | prefix |
34 | } | 34 | } |
35 | 35 | ||
36 | /// Returns the textual content of a doc comment block as a single string. | 36 | /// Returns the textual content of a doc comment node as a single string with prefix and suffix |
37 | /// That is, strips leading `///` (+ optional 1 character of whitespace), | 37 | /// removed. |
38 | /// trailing `*/`, trailing whitespace and then joins the lines. | ||
39 | pub fn doc_comment(&self) -> Option<&str> { | 38 | pub fn doc_comment(&self) -> Option<&str> { |
40 | let kind = self.kind(); | 39 | let kind = self.kind(); |
41 | match kind { | 40 | match kind { |
42 | CommentKind { shape, doc: Some(_) } => { | 41 | CommentKind { shape, doc: Some(_) } => { |
43 | let prefix = kind.prefix(); | 42 | let prefix = kind.prefix(); |
44 | let text = &self.text()[prefix.len()..]; | 43 | let text = &self.text()[prefix.len()..]; |
45 | let ws = text.chars().next().filter(|c| c.is_whitespace()); | 44 | let text = if shape == CommentShape::Block { |
46 | let text = ws.map_or(text, |ws| &text[ws.len_utf8()..]); | 45 | text.strip_suffix("*/").unwrap_or(text) |
47 | match shape { | 46 | } else { |
48 | CommentShape::Block if text.ends_with("*/") => { | 47 | text |
49 | Some(&text[..text.len() - "*/".len()]) | 48 | }; |
50 | } | 49 | Some(text) |
51 | _ => Some(text), | ||
52 | } | ||
53 | } | 50 | } |
54 | _ => None, | 51 | _ => None, |
55 | } | 52 | } |
diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs index 96d4cc997..ddd213637 100644 --- a/crates/syntax/src/ast/traits.rs +++ b/crates/syntax/src/ast/traits.rs | |||
@@ -1,8 +1,6 @@ | |||
1 | //! Various traits that are implemented by ast nodes. | 1 | //! Various traits that are implemented by ast nodes. |
2 | //! | 2 | //! |
3 | //! The implementations are usually trivial, and live in generated.rs | 3 | //! The implementations are usually trivial, and live in generated.rs |
4 | use itertools::Itertools; | ||
5 | |||
6 | use crate::{ | 4 | use crate::{ |
7 | ast::{self, support, AstChildren, AstNode, AstToken}, | 5 | ast::{self, support, AstChildren, AstNode, AstToken}, |
8 | syntax_node::SyntaxElementChildren, | 6 | syntax_node::SyntaxElementChildren, |
@@ -76,10 +74,6 @@ pub trait DocCommentsOwner: AttrsOwner { | |||
76 | fn doc_comments(&self) -> CommentIter { | 74 | fn doc_comments(&self) -> CommentIter { |
77 | CommentIter { iter: self.syntax().children_with_tokens() } | 75 | CommentIter { iter: self.syntax().children_with_tokens() } |
78 | } | 76 | } |
79 | |||
80 | fn doc_comment_text(&self) -> Option<String> { | ||
81 | self.doc_comments().doc_comment_text() | ||
82 | } | ||
83 | } | 77 | } |
84 | 78 | ||
85 | impl CommentIter { | 79 | impl CommentIter { |
@@ -87,12 +81,12 @@ impl CommentIter { | |||
87 | CommentIter { iter: syntax_node.children_with_tokens() } | 81 | CommentIter { iter: syntax_node.children_with_tokens() } |
88 | } | 82 | } |
89 | 83 | ||
90 | /// Returns the textual content of a doc comment block as a single string. | 84 | #[cfg(test)] |
91 | /// That is, strips leading `///` (+ optional 1 character of whitespace), | ||
92 | /// trailing `*/`, trailing whitespace and then joins the lines. | ||
93 | pub fn doc_comment_text(self) -> Option<String> { | 85 | pub fn doc_comment_text(self) -> Option<String> { |
94 | let docs = | 86 | let docs = itertools::Itertools::join( |
95 | self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)).join("\n"); | 87 | &mut self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)), |
88 | "\n", | ||
89 | ); | ||
96 | if docs.is_empty() { | 90 | if docs.is_empty() { |
97 | None | 91 | None |
98 | } else { | 92 | } else { |