Merge #8065
8065: Better handling of block doc comments r=Veykril a=Veykril Moves doc string processing to `Attrs::docs`, as we need the indent info from all comments before being able to know how much to strip Closes #7774 Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
edf11480ce
@ -76,37 +76,23 @@ impl ops::Deref for Attrs {
|
|||||||
impl RawAttrs {
|
impl RawAttrs {
|
||||||
pub(crate) const EMPTY: Self = Self { entries: None };
|
pub(crate) const EMPTY: Self = Self { entries: None };
|
||||||
|
|
||||||
pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Self {
|
pub(crate) fn new(owner: &dyn ast::AttrsOwner, hygiene: &Hygiene) -> Self {
|
||||||
let attrs: Vec<_> = collect_attrs(owner).collect();
|
let entries = collect_attrs(owner)
|
||||||
let entries = if attrs.is_empty() {
|
|
||||||
// Avoid heap allocation
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(
|
|
||||||
attrs
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.flat_map(|(i, attr)| match attr {
|
.flat_map(|(i, attr)| match attr {
|
||||||
Either::Left(attr) => Attr::from_src(attr, hygiene).map(|attr| (i, attr)),
|
Either::Left(attr) => Attr::from_src(attr, hygiene, i as u32),
|
||||||
Either::Right(comment) => comment.doc_comment().map(|doc| {
|
Either::Right(comment) => comment.doc_comment().map(|doc| Attr {
|
||||||
(
|
index: i as u32,
|
||||||
i,
|
|
||||||
Attr {
|
|
||||||
index: 0,
|
|
||||||
input: Some(AttrInput::Literal(SmolStr::new(doc))),
|
input: Some(AttrInput::Literal(SmolStr::new(doc))),
|
||||||
path: ModPath::from(hir_expand::name!(doc)),
|
path: ModPath::from(hir_expand::name!(doc)),
|
||||||
},
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.map(|(i, attr)| Attr { index: i as u32, ..attr })
|
.collect::<Arc<_>>();
|
||||||
.collect(),
|
|
||||||
)
|
Self { entries: if entries.is_empty() { None } else { Some(entries) } }
|
||||||
};
|
|
||||||
Self { entries }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Self {
|
fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::AttrsOwner>) -> Self {
|
||||||
let hygiene = Hygiene::new(db.upcast(), owner.file_id);
|
let hygiene = Hygiene::new(db.upcast(), owner.file_id);
|
||||||
Self::new(owner.value, &hygiene)
|
Self::new(owner.value, &hygiene)
|
||||||
}
|
}
|
||||||
@ -162,7 +148,7 @@ impl RawAttrs {
|
|||||||
let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?;
|
let attr = ast::Attr::parse(&format!("#[{}]", tree)).ok()?;
|
||||||
// FIXME hygiene
|
// FIXME hygiene
|
||||||
let hygiene = Hygiene::new_unhygienic();
|
let hygiene = Hygiene::new_unhygienic();
|
||||||
Attr::from_src(attr, &hygiene).map(|attr| Attr { index, ..attr })
|
Attr::from_src(attr, &hygiene, index)
|
||||||
});
|
});
|
||||||
|
|
||||||
let cfg_options = &crate_graph[krate].cfg_options;
|
let cfg_options = &crate_graph[krate].cfg_options;
|
||||||
@ -192,7 +178,7 @@ impl Attrs {
|
|||||||
Some(it) => {
|
Some(it) => {
|
||||||
let raw_attrs = RawAttrs::from_attrs_owner(
|
let raw_attrs = RawAttrs::from_attrs_owner(
|
||||||
db,
|
db,
|
||||||
it.as_ref().map(|it| it as &dyn AttrsOwner),
|
it.as_ref().map(|it| it as &dyn ast::AttrsOwner),
|
||||||
);
|
);
|
||||||
match mod_data.definition_source(db) {
|
match mod_data.definition_source(db) {
|
||||||
InFile { file_id, value: ModuleSource::SourceFile(file) } => raw_attrs
|
InFile { file_id, value: ModuleSource::SourceFile(file) } => raw_attrs
|
||||||
@ -203,9 +189,9 @@ impl Attrs {
|
|||||||
None => RawAttrs::from_attrs_owner(
|
None => RawAttrs::from_attrs_owner(
|
||||||
db,
|
db,
|
||||||
mod_data.definition_source(db).as_ref().map(|src| match src {
|
mod_data.definition_source(db).as_ref().map(|src| match src {
|
||||||
ModuleSource::SourceFile(file) => file as &dyn AttrsOwner,
|
ModuleSource::SourceFile(file) => file as &dyn ast::AttrsOwner,
|
||||||
ModuleSource::Module(module) => module as &dyn AttrsOwner,
|
ModuleSource::Module(module) => module as &dyn ast::AttrsOwner,
|
||||||
ModuleSource::BlockExpr(block) => block as &dyn AttrsOwner,
|
ModuleSource::BlockExpr(block) => block as &dyn ast::AttrsOwner,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -263,7 +249,7 @@ impl Attrs {
|
|||||||
let mut res = ArenaMap::default();
|
let mut res = ArenaMap::default();
|
||||||
|
|
||||||
for (id, var) in src.value.iter() {
|
for (id, var) in src.value.iter() {
|
||||||
let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn AttrsOwner))
|
let attrs = RawAttrs::from_attrs_owner(db, src.with_value(var as &dyn ast::AttrsOwner))
|
||||||
.filter(db, krate);
|
.filter(db, krate);
|
||||||
|
|
||||||
res.insert(id, attrs)
|
res.insert(id, attrs)
|
||||||
@ -297,7 +283,7 @@ impl Attrs {
|
|||||||
/// Constructs a map that maps the lowered `Attr`s in this `Attrs` back to its original syntax nodes.
|
/// Constructs a map that maps the lowered `Attr`s in this `Attrs` back to its original syntax nodes.
|
||||||
///
|
///
|
||||||
/// `owner` must be the original owner of the attributes.
|
/// `owner` must be the original owner of the attributes.
|
||||||
pub fn source_map(&self, owner: &dyn AttrsOwner) -> AttrSourceMap {
|
pub fn source_map(&self, owner: &dyn ast::AttrsOwner) -> AttrSourceMap {
|
||||||
AttrSourceMap { attrs: collect_attrs(owner).collect() }
|
AttrSourceMap { attrs: collect_attrs(owner).collect() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,15 +311,34 @@ impl Attrs {
|
|||||||
AttrInput::Literal(s) => Some(s),
|
AttrInput::Literal(s) => Some(s),
|
||||||
AttrInput::TokenTree(_) => None,
|
AttrInput::TokenTree(_) => None,
|
||||||
});
|
});
|
||||||
// FIXME: Replace `Itertools::intersperse` with `Iterator::intersperse[_with]` until the
|
let indent = docs
|
||||||
// libstd api gets stabilized (https://github.com/rust-lang/rust/issues/79524).
|
.clone()
|
||||||
let docs = Itertools::intersperse(docs, &SmolStr::new_inline("\n"))
|
.flat_map(|s| s.lines())
|
||||||
.map(|it| it.as_str())
|
.filter(|line| !line.chars().all(|c| c.is_whitespace()))
|
||||||
.collect::<String>();
|
.map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
|
||||||
if docs.is_empty() {
|
.min()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let mut buf = String::new();
|
||||||
|
for doc in docs {
|
||||||
|
// str::lines doesn't yield anything for the empty string
|
||||||
|
if !doc.is_empty() {
|
||||||
|
buf.extend(Itertools::intersperse(
|
||||||
|
doc.lines().map(|line| {
|
||||||
|
line.char_indices()
|
||||||
|
.nth(indent)
|
||||||
|
.map_or(line, |(offset, _)| &line[offset..])
|
||||||
|
.trim_end()
|
||||||
|
}),
|
||||||
|
"\n",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
buf.push('\n');
|
||||||
|
}
|
||||||
|
buf.pop();
|
||||||
|
if buf.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(Documentation(docs))
|
Some(Documentation(buf))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -407,7 +412,7 @@ pub enum AttrInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Attr {
|
impl Attr {
|
||||||
fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
|
fn from_src(ast: ast::Attr, hygiene: &Hygiene, index: u32) -> Option<Attr> {
|
||||||
let path = ModPath::from_src(ast.path()?, hygiene)?;
|
let path = ModPath::from_src(ast.path()?, hygiene)?;
|
||||||
let input = if let Some(lit) = ast.literal() {
|
let input = if let Some(lit) = ast.literal() {
|
||||||
let value = match lit.kind() {
|
let value = match lit.kind() {
|
||||||
@ -420,7 +425,7 @@ impl Attr {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Some(Attr { index: 0, path, input })
|
Some(Attr { index, path, input })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps this lowered `Attr` back to its original syntax node.
|
/// Maps this lowered `Attr` back to its original syntax node.
|
||||||
@ -429,7 +434,7 @@ impl Attr {
|
|||||||
///
|
///
|
||||||
/// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
|
/// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
|
||||||
/// the attribute represented by `Attr`.
|
/// the attribute represented by `Attr`.
|
||||||
pub fn to_src(&self, owner: &dyn AttrsOwner) -> Either<ast::Attr, ast::Comment> {
|
pub fn to_src(&self, owner: &dyn ast::AttrsOwner) -> Either<ast::Attr, ast::Comment> {
|
||||||
collect_attrs(owner).nth(self.index as usize).unwrap_or_else(|| {
|
collect_attrs(owner).nth(self.index as usize).unwrap_or_else(|| {
|
||||||
panic!("cannot find `Attr` at index {} in {}", self.index, owner.syntax())
|
panic!("cannot find `Attr` at index {} in {}", self.index, owner.syntax())
|
||||||
})
|
})
|
||||||
@ -508,7 +513,7 @@ impl<'a> AttrQuery<'a> {
|
|||||||
self.attrs().next().is_some()
|
self.attrs().next().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attrs(self) -> impl Iterator<Item = &'a Attr> {
|
pub fn attrs(self) -> impl Iterator<Item = &'a Attr> + Clone {
|
||||||
let key = self.key;
|
let key = self.key;
|
||||||
self.attrs
|
self.attrs
|
||||||
.iter()
|
.iter()
|
||||||
@ -521,7 +526,7 @@ where
|
|||||||
N: ast::AttrsOwner,
|
N: ast::AttrsOwner,
|
||||||
{
|
{
|
||||||
let src = InFile::new(src.file_id, src.to_node(db.upcast()));
|
let src = InFile::new(src.file_id, src.to_node(db.upcast()));
|
||||||
RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))
|
RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn ast::AttrsOwner))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
|
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
|
|||||||
tree.raw_attrs(mod_item.into()).clone()
|
tree.raw_attrs(mod_item.into()).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_attrs(owner: &dyn AttrsOwner) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> {
|
fn collect_attrs(
|
||||||
|
owner: &dyn ast::AttrsOwner,
|
||||||
|
) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> {
|
||||||
let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
|
let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
|
||||||
.map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs))));
|
.map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs))));
|
||||||
|
|
||||||
|
@ -95,12 +95,10 @@ fn extract_positioned_link_from_comment(
|
|||||||
let comment_range = comment.syntax().text_range();
|
let comment_range = comment.syntax().text_range();
|
||||||
let doc_comment = comment.doc_comment()?;
|
let doc_comment = comment.doc_comment()?;
|
||||||
let def_links = extract_definitions_from_markdown(doc_comment);
|
let def_links = extract_definitions_from_markdown(doc_comment);
|
||||||
|
let start = comment_range.start() + TextSize::from(comment.prefix().len() as u32);
|
||||||
let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| {
|
let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| {
|
||||||
let matched_position = comment_range.start() + TextSize::from(def_link_range.start as u32);
|
let matched_position = start + TextSize::from(def_link_range.start as u32);
|
||||||
match position.offset.checked_sub(matched_position) {
|
position.offset.checked_sub(matched_position).unwrap_or_else(|| comment_range.end())
|
||||||
Some(distance) => distance,
|
|
||||||
None => comment_range.end(),
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
Some((def_link.to_string(), *ns))
|
Some((def_link.to_string(), *ns))
|
||||||
}
|
}
|
||||||
|
@ -1533,12 +1533,21 @@ fn my() {}
|
|||||||
fn test_hover_struct_doc_comment() {
|
fn test_hover_struct_doc_comment() {
|
||||||
check(
|
check(
|
||||||
r#"
|
r#"
|
||||||
/// bar docs
|
/// This is an example
|
||||||
|
/// multiline doc
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let five = 5;
|
||||||
|
///
|
||||||
|
/// assert_eq!(6, my_crate::add_one(5));
|
||||||
|
/// ```
|
||||||
struct Bar;
|
struct Bar;
|
||||||
|
|
||||||
fn foo() { let bar = Ba$0r; }
|
fn foo() { let bar = Ba$0r; }
|
||||||
"#,
|
"#,
|
||||||
expect![[r#"
|
expect![[r##"
|
||||||
*Bar*
|
*Bar*
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -1551,8 +1560,17 @@ fn foo() { let bar = Ba$0r; }
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
bar docs
|
This is an example
|
||||||
"#]],
|
multiline doc
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
```
|
||||||
|
let five = 5;
|
||||||
|
|
||||||
|
assert_eq!(6, my_crate::add_one(5));
|
||||||
|
```
|
||||||
|
"##]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3423,6 +3441,40 @@ mod Foo$0 {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_doc_block_style_indentend() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
/**
|
||||||
|
foo
|
||||||
|
```rust
|
||||||
|
let x = 3;
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
fn foo$0() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*foo*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn foo()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
foo
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let x = 3;
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_comments_dont_highlight_parent() {
|
fn hover_comments_dont_highlight_parent() {
|
||||||
check_hover_no_result(
|
check_hover_no_result(
|
||||||
|
@ -576,6 +576,20 @@ fn should_have_runnable_1() {}
|
|||||||
/// ```
|
/// ```
|
||||||
fn should_have_runnable_2() {}
|
fn should_have_runnable_2() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
```rust
|
||||||
|
let z = 55;
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
fn should_have_no_runnable_3() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
```rust
|
||||||
|
let z = 55;
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
fn should_have_no_runnable_4() {}
|
||||||
|
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// let z = 55;
|
/// let z = 55;
|
||||||
/// ```
|
/// ```
|
||||||
@ -616,7 +630,7 @@ fn should_have_no_runnable_6() {}
|
|||||||
struct StructWithRunnable(String);
|
struct StructWithRunnable(String);
|
||||||
|
|
||||||
"#,
|
"#,
|
||||||
&[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST],
|
&[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST],
|
||||||
expect![[r#"
|
expect![[r#"
|
||||||
[
|
[
|
||||||
Runnable {
|
Runnable {
|
||||||
@ -682,7 +696,37 @@ struct StructWithRunnable(String);
|
|||||||
file_id: FileId(
|
file_id: FileId(
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
full_range: 756..821,
|
full_range: 256..320,
|
||||||
|
name: "should_have_no_runnable_3",
|
||||||
|
},
|
||||||
|
kind: DocTest {
|
||||||
|
test_id: Path(
|
||||||
|
"should_have_no_runnable_3",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cfg: None,
|
||||||
|
},
|
||||||
|
Runnable {
|
||||||
|
nav: NavigationTarget {
|
||||||
|
file_id: FileId(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
full_range: 322..398,
|
||||||
|
name: "should_have_no_runnable_4",
|
||||||
|
},
|
||||||
|
kind: DocTest {
|
||||||
|
test_id: Path(
|
||||||
|
"should_have_no_runnable_4",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
cfg: None,
|
||||||
|
},
|
||||||
|
Runnable {
|
||||||
|
nav: NavigationTarget {
|
||||||
|
file_id: FileId(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
full_range: 900..965,
|
||||||
name: "StructWithRunnable",
|
name: "StructWithRunnable",
|
||||||
},
|
},
|
||||||
kind: DocTest {
|
kind: DocTest {
|
||||||
|
@ -53,15 +53,15 @@ pub fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo>
|
|||||||
|
|
||||||
match callable.kind() {
|
match callable.kind() {
|
||||||
hir::CallableKind::Function(func) => {
|
hir::CallableKind::Function(func) => {
|
||||||
res.doc = func.docs(db).map(|it| it.as_str().to_string());
|
res.doc = func.docs(db).map(|it| it.into());
|
||||||
format_to!(res.signature, "fn {}", func.name(db));
|
format_to!(res.signature, "fn {}", func.name(db));
|
||||||
}
|
}
|
||||||
hir::CallableKind::TupleStruct(strukt) => {
|
hir::CallableKind::TupleStruct(strukt) => {
|
||||||
res.doc = strukt.docs(db).map(|it| it.as_str().to_string());
|
res.doc = strukt.docs(db).map(|it| it.into());
|
||||||
format_to!(res.signature, "struct {}", strukt.name(db));
|
format_to!(res.signature, "struct {}", strukt.name(db));
|
||||||
}
|
}
|
||||||
hir::CallableKind::TupleEnumVariant(variant) => {
|
hir::CallableKind::TupleEnumVariant(variant) => {
|
||||||
res.doc = variant.docs(db).map(|it| it.as_str().to_string());
|
res.doc = variant.docs(db).map(|it| it.into());
|
||||||
format_to!(
|
format_to!(
|
||||||
res.signature,
|
res.signature,
|
||||||
"enum {}::{}",
|
"enum {}::{}",
|
||||||
|
@ -118,7 +118,7 @@ fn test_doc_comment_none() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert!(module.doc_comment_text().is_none());
|
assert!(module.doc_comments().doc_comment_text().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -133,7 +133,7 @@ fn test_outer_doc_comment_of_items() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert_eq!("doc", module.doc_comment_text().unwrap());
|
assert_eq!(" doc", module.doc_comments().doc_comment_text().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -148,7 +148,7 @@ fn test_inner_doc_comment_of_items() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert!(module.doc_comment_text().is_none());
|
assert!(module.doc_comments().doc_comment_text().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -162,7 +162,7 @@ fn test_doc_comment_of_statics() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let st = file.syntax().descendants().find_map(Static::cast).unwrap();
|
let st = file.syntax().descendants().find_map(Static::cast).unwrap();
|
||||||
assert_eq!("Number of levels", st.doc_comment_text().unwrap());
|
assert_eq!(" Number of levels", st.doc_comments().doc_comment_text().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -181,7 +181,10 @@ fn test_doc_comment_preserves_indents() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert_eq!("doc1\n```\nfn foo() {\n // ...\n}\n```", module.doc_comment_text().unwrap());
|
assert_eq!(
|
||||||
|
" doc1\n ```\n fn foo() {\n // ...\n }\n ```",
|
||||||
|
module.doc_comments().doc_comment_text().unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -198,7 +201,7 @@ fn test_doc_comment_preserves_newlines() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert_eq!("this\nis\nmod\nfoo", module.doc_comment_text().unwrap());
|
assert_eq!(" this\n is\n mod\n foo", module.doc_comments().doc_comment_text().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -212,7 +215,7 @@ fn test_doc_comment_single_line_block_strips_suffix() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert_eq!("this is mod foo", module.doc_comment_text().unwrap());
|
assert_eq!(" this is mod foo", module.doc_comments().doc_comment_text().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -226,7 +229,7 @@ fn test_doc_comment_single_line_block_strips_suffix_whitespace() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert_eq!("this is mod foo ", module.doc_comment_text().unwrap());
|
assert_eq!(" this is mod foo ", module.doc_comments().doc_comment_text().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -245,8 +248,8 @@ fn test_doc_comment_multi_line_block_strips_suffix() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
" this\n is\n mod foo\n ",
|
"\n this\n is\n mod foo\n ",
|
||||||
module.doc_comment_text().unwrap()
|
module.doc_comments().doc_comment_text().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +263,7 @@ fn test_comments_preserve_trailing_whitespace() {
|
|||||||
let def = file.syntax().descendants().find_map(Struct::cast).unwrap();
|
let def = file.syntax().descendants().find_map(Struct::cast).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
" Representation of a Realm. \n In the specification these are called Realm Records.",
|
" Representation of a Realm. \n In the specification these are called Realm Records.",
|
||||||
def.doc_comment_text().unwrap()
|
def.doc_comments().doc_comment_text().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +279,7 @@ fn test_four_slash_line_comment() {
|
|||||||
.ok()
|
.ok()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||||
assert_eq!("doc comment", module.doc_comment_text().unwrap());
|
assert_eq!(" doc comment", module.doc_comments().doc_comment_text().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -33,23 +33,20 @@ impl ast::Comment {
|
|||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the textual content of a doc comment block as a single string.
|
/// Returns the textual content of a doc comment node as a single string with prefix and suffix
|
||||||
/// That is, strips leading `///` (+ optional 1 character of whitespace),
|
/// removed.
|
||||||
/// trailing `*/`, trailing whitespace and then joins the lines.
|
|
||||||
pub fn doc_comment(&self) -> Option<&str> {
|
pub fn doc_comment(&self) -> Option<&str> {
|
||||||
let kind = self.kind();
|
let kind = self.kind();
|
||||||
match kind {
|
match kind {
|
||||||
CommentKind { shape, doc: Some(_) } => {
|
CommentKind { shape, doc: Some(_) } => {
|
||||||
let prefix = kind.prefix();
|
let prefix = kind.prefix();
|
||||||
let text = &self.text()[prefix.len()..];
|
let text = &self.text()[prefix.len()..];
|
||||||
let ws = text.chars().next().filter(|c| c.is_whitespace());
|
let text = if shape == CommentShape::Block {
|
||||||
let text = ws.map_or(text, |ws| &text[ws.len_utf8()..]);
|
text.strip_suffix("*/").unwrap_or(text)
|
||||||
match shape {
|
} else {
|
||||||
CommentShape::Block if text.ends_with("*/") => {
|
text
|
||||||
Some(&text[..text.len() - "*/".len()])
|
};
|
||||||
}
|
Some(text)
|
||||||
_ => Some(text),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
//! Various traits that are implemented by ast nodes.
|
//! Various traits that are implemented by ast nodes.
|
||||||
//!
|
//!
|
||||||
//! The implementations are usually trivial, and live in generated.rs
|
//! The implementations are usually trivial, and live in generated.rs
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{self, support, AstChildren, AstNode, AstToken},
|
ast::{self, support, AstChildren, AstNode, AstToken},
|
||||||
syntax_node::SyntaxElementChildren,
|
syntax_node::SyntaxElementChildren,
|
||||||
@ -76,10 +74,6 @@ pub trait DocCommentsOwner: AttrsOwner {
|
|||||||
fn doc_comments(&self) -> CommentIter {
|
fn doc_comments(&self) -> CommentIter {
|
||||||
CommentIter { iter: self.syntax().children_with_tokens() }
|
CommentIter { iter: self.syntax().children_with_tokens() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn doc_comment_text(&self) -> Option<String> {
|
|
||||||
self.doc_comments().doc_comment_text()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommentIter {
|
impl CommentIter {
|
||||||
@ -87,12 +81,12 @@ impl CommentIter {
|
|||||||
CommentIter { iter: syntax_node.children_with_tokens() }
|
CommentIter { iter: syntax_node.children_with_tokens() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the textual content of a doc comment block as a single string.
|
#[cfg(test)]
|
||||||
/// That is, strips leading `///` (+ optional 1 character of whitespace),
|
|
||||||
/// trailing `*/`, trailing whitespace and then joins the lines.
|
|
||||||
pub fn doc_comment_text(self) -> Option<String> {
|
pub fn doc_comment_text(self) -> Option<String> {
|
||||||
let docs =
|
let docs = itertools::Itertools::join(
|
||||||
self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)).join("\n");
|
&mut self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)),
|
||||||
|
"\n",
|
||||||
|
);
|
||||||
if docs.is_empty() {
|
if docs.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user