aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir_def/src/attr.rs105
-rw-r--r--crates/ide/src/goto_definition.rs8
-rw-r--r--crates/ide/src/hover.rs60
-rw-r--r--crates/ide/src/runnables.rs48
-rw-r--r--crates/ide_completion/src/lib.rs2
-rw-r--r--crates/ide_db/src/call_info.rs6
-rw-r--r--crates/ide_db/src/call_info/tests.rs82
-rw-r--r--crates/syntax/src/ast.rs29
-rw-r--r--crates/syntax/src/ast/token_ext.rs19
-rw-r--r--crates/syntax/src/ast/traits.rs16
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 {
76impl RawAttrs { 76impl 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
409impl Attr { 414impl 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
527fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs { 532fn 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
533fn collect_attrs(owner: &dyn AttrsOwner) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> { 538fn 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/// ```
1537struct Bar; 1546struct Bar;
1538 1547
1539fn foo() { let bar = Ba$0r; } 1548fn 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*/
3454fn 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/// ```
577fn should_have_runnable_2() {} 577fn should_have_runnable_2() {}
578 578
579/**
580```rust
581let z = 55;
582```
583*/
584fn should_have_no_runnable_3() {}
585
586/**
587 ```rust
588 let z = 55;
589 ```
590*/
591fn 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() {}
616struct StructWithRunnable(String); 630struct 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
4use itertools::Itertools;
5
6use crate::{ 4use 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
85impl CommentIter { 79impl 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 {