From 4c655c01f31ceffae4f8219f9706992e0e7f188a Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Sat, 30 May 2020 14:21:06 -0400 Subject: Enable hover and autocomplete docs on macro generated items --- crates/ra_hir_def/src/attr.rs | 8 +++++- crates/ra_hir_def/src/docs.rs | 43 +++++++++++++++++++++++++++-- crates/ra_hir_expand/src/name.rs | 1 + crates/ra_ide/src/hover.rs | 55 ++++++++++++++++++++++++++++++++++---- crates/ra_syntax/src/ast/traits.rs | 13 +++++++-- 5 files changed, 110 insertions(+), 10 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 8b6c0bede..2eeba0572 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -87,12 +87,18 @@ impl Attrs { } pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { + let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map( + |docs_text| Attr { + input: Some(AttrInput::Literal(SmolStr::new(docs_text))), + path: ModPath::from(hir_expand::name!(doc)), + }, + ); let mut attrs = owner.attrs().peekable(); let entries = if attrs.peek().is_none() { // Avoid heap allocation None } else { - Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect()) + Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect()) }; Attrs { entries } } diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index b221ae1ce..74b9f8199 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -70,6 +70,45 @@ impl Documentation { } } -pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option { - node.doc_comment_text().map(|it| Documentation::new(&it)) +pub(crate) fn docs_from_ast(node: &N) -> Option +where + N: ast::DocCommentsOwner + ast::AttrsOwner, +{ + let doc_comment_text = node.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(node); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + docs.map(|it| Documentation::new(&it)) +} + +fn merge_doc_comments_and_attrs( + doc_comment_text: Option, + doc_attr_text: Option, +) -> Option { + match (doc_comment_text, doc_attr_text) { + (Some(mut comment_text), Some(attr_text)) => { + comment_text.push_str("\n\n"); + comment_text.push_str(&attr_text); + Some(comment_text) + } + (Some(comment_text), None) => Some(comment_text), + (None, Some(attr_text)) => Some(attr_text), + (None, None) => None, + } +} + +fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { + let mut docs = String::new(); + for attr in owner.attrs() { + if let Some(("doc", value)) = + attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) + { + docs.push_str(value); + docs.push_str("\n\n"); + } + } + if docs.is_empty() { + None + } else { + Some(docs) + } } diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index ea495cb11..660bdfe33 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs @@ -153,6 +153,7 @@ pub mod known { str, // Special names macro_rules, + doc, // Components of known path (value or mod name) std, core, diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index d96cb5596..e25a7dacf 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -169,13 +169,19 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { let src = it.source(db); - hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) + let doc_comment_text = src.value.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&src.value); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, Some(macro_label(&src.value)), mod_path) } Definition::Field(it) => { let src = it.source(db); match src.value { FieldSource::Named(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let doc_comment_text = it.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&it); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, it.short_label(), mod_path) } _ => None, } @@ -183,7 +189,10 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option match it { ModuleDef::Module(it) => match it.definition_source(db).value { ModuleSource::Module(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let doc_comment_text = it.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&it); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, it.short_label(), mod_path) } _ => None, }, @@ -208,10 +217,46 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option(db: &RootDatabase, def: D, mod_path: Option) -> Option where D: HasSource, - A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, + A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, { let src = def.source(db); - hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) + let doc_comment_text = src.value.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(&src.value); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + hover_text(docs, src.value.short_label(), mod_path) + } +} + +fn merge_doc_comments_and_attrs( + doc_comment_text: Option, + doc_attr_text: Option, +) -> Option { + match (doc_comment_text, doc_attr_text) { + (Some(mut comment_text), Some(attr_text)) => { + comment_text.push_str("\n\n"); + comment_text.push_str(&attr_text); + Some(comment_text) + } + (Some(comment_text), None) => Some(comment_text), + (None, Some(attr_text)) => Some(attr_text), + (None, None) => None, + } +} + +fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { + let mut docs = String::new(); + for attr in owner.attrs() { + if let Some(("doc", value)) = + attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) + { + docs.push_str(value); + docs.push_str("\n\n"); + } + } + if docs.is_empty() { + None + } else { + Some(docs) } } diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index bfc05e08b..a8f2454fd 100644 --- a/crates/ra_syntax/src/ast/traits.rs +++ b/crates/ra_syntax/src/ast/traits.rs @@ -83,13 +83,22 @@ pub trait DocCommentsOwner: AstNode { CommentIter { iter: self.syntax().children_with_tokens() } } + fn doc_comment_text(&self) -> Option { + self.doc_comments().doc_comment_text() + } +} + +impl CommentIter { + pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> CommentIter { + CommentIter { iter: syntax_node.children_with_tokens() } + } + /// Returns the textual content of a doc comment block as a single string. /// That is, strips leading `///` (+ optional 1 character of whitespace), /// trailing `*/`, trailing whitespace and then joins the lines. - fn doc_comment_text(&self) -> Option { + pub fn doc_comment_text(self) -> Option { let mut has_comments = false; let docs = self - .doc_comments() .filter(|comment| comment.kind().doc.is_some()) .map(|comment| { has_comments = true; -- cgit v1.2.3 From 5837acce532e0cd65a1c0cb8c03cc18a4c22f327 Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Sun, 31 May 2020 11:33:48 -0400 Subject: Add basic hover and completion doc tests for macro generated items --- crates/ra_hir_def/src/docs.rs | 2 +- crates/ra_ide/src/completion.rs | 78 ++++++++++++++++++++++++++++++ crates/ra_ide/src/hover.rs | 104 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 182 insertions(+), 2 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index 74b9f8199..ab43cd3d3 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -109,6 +109,6 @@ fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { if docs.is_empty() { None } else { - Some(docs) + Some(docs.trim_end_matches("\n\n").to_owned()) } } diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index d890b69d2..a721e23c6 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -125,3 +125,81 @@ pub(crate) fn completions( Some(acc) } + +#[cfg(test)] +mod tests { + use crate::completion::completion_config::CompletionConfig; + use crate::mock_analysis::analysis_and_position; + + struct DetailAndDocumentation<'a> { + detail: &'a str, + documentation: &'a str, + } + + fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) { + let (analysis, position) = analysis_and_position(fixture); + let config = CompletionConfig::default(); + let completions = analysis.completions(&config, position).unwrap().unwrap(); + for item in completions { + if item.detail() == Some(expected.detail) { + let opt = item.documentation(); + let doc = opt.as_ref().map(|it| it.as_str()); + assert_eq!(doc, Some(expected.documentation)); + return; + } + } + panic!("completion detail not found: {}", expected.detail) + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" }, + ); + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, + ); + } +} diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index e25a7dacf..731fc3673 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -256,7 +256,7 @@ fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { if docs.is_empty() { None } else { - Some(docs) + Some(docs.trim_end_matches("\n\n").to_owned()) } } @@ -996,4 +996,106 @@ fn func(foo: i32) { if true { <|>foo; }; } &["mod my"], ); } + + #[test] + fn test_hover_struct_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + #[doc = "bar docs"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr_multiple_and_mixed() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs 0 + #[doc = "bar docs 1"] + #[doc = "bar docs 2"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], + ); + } } -- cgit v1.2.3 From 85c4edb0afbc7cc855c434e5e7ec69aa469e0c4b Mon Sep 17 00:00:00 2001 From: Aaron Loucks Date: Wed, 3 Jun 2020 06:14:56 -0400 Subject: Consolidate documentation expansion and merging Removes the duplicated `expand_doc_attrs` and `merge_doc_comments_and_attrs` functions from `ra_ide` and exposes the same functionality via `ra_hir::Documentation::from_ast`. --- crates/ra_hir_def/src/docs.rs | 7 +++++ crates/ra_ide/src/hover.rs | 60 +++++-------------------------------------- 2 files changed, 14 insertions(+), 53 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index ab43cd3d3..2630b3d89 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -29,6 +29,13 @@ impl Documentation { Documentation(s.into()) } + pub fn from_ast(node: &N) -> Option + where + N: ast::DocCommentsOwner + ast::AttrsOwner, + { + docs_from_ast(node) + } + pub fn as_str(&self) -> &str { &*self.0 } diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 731fc3673..9636cd0d6 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,8 +1,8 @@ use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, + Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, + ModuleDef, ModuleSource, Semantics, }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -10,12 +10,7 @@ use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ - ast::{self, DocCommentsOwner}, - match_ast, AstNode, - SyntaxKind::*, - SyntaxToken, TokenAtOffset, -}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{ display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, @@ -169,18 +164,14 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { let src = it.source(db); - let doc_comment_text = src.value.doc_comment_text(); - let doc_attr_text = expand_doc_attrs(&src.value); - let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + let docs = Documentation::from_ast(&src.value).map(Into::into); hover_text(docs, Some(macro_label(&src.value)), mod_path) } Definition::Field(it) => { let src = it.source(db); match src.value { FieldSource::Named(it) => { - let doc_comment_text = it.doc_comment_text(); - let doc_attr_text = expand_doc_attrs(&it); - let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + let docs = Documentation::from_ast(&it).map(Into::into); hover_text(docs, it.short_label(), mod_path) } _ => None, @@ -189,9 +180,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option match it { ModuleDef::Module(it) => match it.definition_source(db).value { ModuleSource::Module(it) => { - let doc_comment_text = it.doc_comment_text(); - let doc_attr_text = expand_doc_attrs(&it); - let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + let docs = Documentation::from_ast(&it).map(Into::into); hover_text(docs, it.short_label(), mod_path) } _ => None, @@ -220,46 +209,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option, - doc_attr_text: Option, -) -> Option { - match (doc_comment_text, doc_attr_text) { - (Some(mut comment_text), Some(attr_text)) => { - comment_text.push_str("\n\n"); - comment_text.push_str(&attr_text); - Some(comment_text) - } - (Some(comment_text), None) => Some(comment_text), - (None, Some(attr_text)) => Some(attr_text), - (None, None) => None, - } -} - -fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { - let mut docs = String::new(); - for attr in owner.attrs() { - if let Some(("doc", value)) = - attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) - { - docs.push_str(value); - docs.push_str("\n\n"); - } - } - if docs.is_empty() { - None - } else { - Some(docs.trim_end_matches("\n\n").to_owned()) - } -} - fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { -- cgit v1.2.3