aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-12-08 13:23:12 +0000
committerGitHub <[email protected]>2020-12-08 13:23:12 +0000
commit2aa7f2ece517a5202421f7a4a7cdd99bd1862ac8 (patch)
treefcdf682d2175735262a7d733043bb510fd86526e /crates
parent4d4f11925f793c45560c45c088d4b3139c2c171c (diff)
parent3174e941dbb7d91bad011ba51a9b55736996b36c (diff)
Merge #6750
6750: Remove documentation query, move doc handling to attributes r=matklad a=Veykril Fixes #3182 Removes the documentation query in favor of `Attrs::docs`. Attrs already handlded doc comments partially but the alloc saving check was wrong so it only worked when other attributes existed as well. Unfortunately the `new` constructor has to do an intermediate allocation now because we need to keep the order of mixed doc attributes and doc comments. I've also partially adjusted the `hover` module to have its tests check the changes, it still has some `HasSource` trait usage due to the `ShortLabel` trait usage, as that is only implemented on the Ast parts and not the Hir, should this ideally be implemented for the Hir types as well?(would be a follow up PR of course) Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/attrs.rs7
-rw-r--r--crates/hir/src/db.rs12
-rw-r--r--crates/hir/src/lib.rs3
-rw-r--r--crates/hir_def/src/attr.rs68
-rw-r--r--crates/hir_def/src/db.rs6
-rw-r--r--crates/hir_def/src/docs.rs121
-rw-r--r--crates/hir_def/src/lib.rs1
-rw-r--r--crates/hir_def/src/nameres/tests/mod_resolution.rs2
-rw-r--r--crates/ide/src/hover.rs64
-rw-r--r--crates/ide_db/src/apply_change.rs1
-rw-r--r--crates/syntax/src/ast/token_ext.rs27
-rw-r--r--crates/syntax/src/ast/traits.rs38
12 files changed, 137 insertions, 213 deletions
diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs
index c3e820d89..1f2ee2580 100644
--- a/crates/hir/src/attrs.rs
+++ b/crates/hir/src/attrs.rs
@@ -1,6 +1,9 @@
1//! Attributes & documentation for hir types. 1//! Attributes & documentation for hir types.
2use hir_def::{ 2use hir_def::{
3 attr::Attrs, docs::Documentation, path::ModPath, resolver::HasResolver, AttrDefId, ModuleDefId, 3 attr::{Attrs, Documentation},
4 path::ModPath,
5 resolver::HasResolver,
6 AttrDefId, ModuleDefId,
4}; 7};
5use hir_expand::hygiene::Hygiene; 8use hir_expand::hygiene::Hygiene;
6use hir_ty::db::HirDatabase; 9use hir_ty::db::HirDatabase;
@@ -38,7 +41,7 @@ macro_rules! impl_has_attrs {
38 } 41 }
39 fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> { 42 fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
40 let def = AttrDefId::$def_id(self.into()); 43 let def = AttrDefId::$def_id(self.into());
41 db.documentation(def) 44 db.attrs(def).docs()
42 } 45 }
43 fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> { 46 fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace>) -> Option<ModuleDef> {
44 let def = AttrDefId::$def_id(self.into()); 47 let def = AttrDefId::$def_id(self.into());
diff --git a/crates/hir/src/db.rs b/crates/hir/src/db.rs
index 8c767b249..8d949b264 100644
--- a/crates/hir/src/db.rs
+++ b/crates/hir/src/db.rs
@@ -2,12 +2,12 @@
2 2
3pub use hir_def::db::{ 3pub use hir_def::db::{
4 AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery, 4 AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery,
5 CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, 5 CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, EnumDataQuery, ExprScopesQuery,
6 ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery, 6 FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery, InternConstQuery,
7 InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, 7 InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, InternImplQuery,
8 InternImplQuery, InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, 8 InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery,
9 InternUnionQuery, ItemTreeQuery, LangItemQuery, ModuleLangItemsQuery, StaticDataQuery, 9 ItemTreeQuery, LangItemQuery, ModuleLangItemsQuery, StaticDataQuery, StructDataQuery,
10 StructDataQuery, TraitDataQuery, TypeAliasDataQuery, UnionDataQuery, 10 TraitDataQuery, TypeAliasDataQuery, UnionDataQuery,
11}; 11};
12pub use hir_expand::db::{ 12pub use hir_expand::db::{
13 AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, 13 AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 93bdb4472..c7c7377d7 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -44,10 +44,9 @@ pub use crate::{
44 44
45pub use hir_def::{ 45pub use hir_def::{
46 adt::StructKind, 46 adt::StructKind,
47 attr::Attrs, 47 attr::{Attrs, Documentation},
48 body::scope::ExprScopes, 48 body::scope::ExprScopes,
49 builtin_type::BuiltinType, 49 builtin_type::BuiltinType,
50 docs::Documentation,
51 find_path::PrefixKind, 50 find_path::PrefixKind,
52 import_map, 51 import_map,
53 item_scope::ItemInNs, 52 item_scope::ItemInNs,
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index b2ce7ca3c..12f4b02e2 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -5,10 +5,11 @@ use std::{ops, sync::Arc};
5use cfg::{CfgExpr, CfgOptions}; 5use cfg::{CfgExpr, CfgOptions};
6use either::Either; 6use either::Either;
7use hir_expand::{hygiene::Hygiene, AstId, InFile}; 7use hir_expand::{hygiene::Hygiene, AstId, InFile};
8use itertools::Itertools;
8use mbe::ast_to_token_tree; 9use mbe::ast_to_token_tree;
9use syntax::{ 10use syntax::{
10 ast::{self, AstNode, AttrsOwner}, 11 ast::{self, AstNode, AttrsOwner},
11 SmolStr, 12 AstToken, SmolStr,
12}; 13};
13use tt::Subtree; 14use tt::Subtree;
14 15
@@ -21,6 +22,22 @@ use crate::{
21 AdtId, AttrDefId, Lookup, 22 AdtId, AttrDefId, Lookup,
22}; 23};
23 24
25/// Holds documentation
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Documentation(String);
28
29impl Documentation {
30 pub fn as_str(&self) -> &str {
31 &self.0
32 }
33}
34
35impl Into<String> for Documentation {
36 fn into(self) -> String {
37 self.0
38 }
39}
40
24#[derive(Default, Debug, Clone, PartialEq, Eq)] 41#[derive(Default, Debug, Clone, PartialEq, Eq)]
25pub struct Attrs { 42pub struct Attrs {
26 entries: Option<Arc<[Attr]>>, 43 entries: Option<Arc<[Attr]>>,
@@ -93,18 +110,25 @@ impl Attrs {
93 } 110 }
94 111
95 pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { 112 pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
96 let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map( 113 let docs = ast::CommentIter::from_syntax_node(owner.syntax()).map(|docs_text| {
97 |docs_text| Attr { 114 (
98 input: Some(AttrInput::Literal(SmolStr::new(docs_text))), 115 docs_text.syntax().text_range().start(),
99 path: ModPath::from(hir_expand::name!(doc)), 116 docs_text.doc_comment().map(|doc| Attr {
100 }, 117 input: Some(AttrInput::Literal(SmolStr::new(doc))),
101 ); 118 path: ModPath::from(hir_expand::name!(doc)),
102 let mut attrs = owner.attrs().peekable(); 119 }),
103 let entries = if attrs.peek().is_none() { 120 )
121 });
122 let attrs = owner
123 .attrs()
124 .map(|attr| (attr.syntax().text_range().start(), Attr::from_src(attr, hygiene)));
125 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
126 let attrs: Vec<_> = docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).collect();
127 let entries = if attrs.is_empty() {
104 // Avoid heap allocation 128 // Avoid heap allocation
105 None 129 None
106 } else { 130 } else {
107 Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect()) 131 Some(attrs.into_iter().flat_map(|(_, attr)| attr).collect())
108 }; 132 };
109 Attrs { entries } 133 Attrs { entries }
110 } 134 }
@@ -140,6 +164,24 @@ impl Attrs {
140 Some(cfg) => cfg_options.check(&cfg) != Some(false), 164 Some(cfg) => cfg_options.check(&cfg) != Some(false),
141 } 165 }
142 } 166 }
167
168 pub fn docs(&self) -> Option<Documentation> {
169 let docs = self
170 .by_key("doc")
171 .attrs()
172 .flat_map(|attr| match attr.input.as_ref()? {
173 AttrInput::Literal(s) => Some(s),
174 AttrInput::TokenTree(_) => None,
175 })
176 .intersperse(&SmolStr::new_inline("\n"))
177 .map(|it| it.as_str())
178 .collect::<String>();
179 if docs.is_empty() {
180 None
181 } else {
182 Some(Documentation(docs.into()))
183 }
184 }
143} 185}
144 186
145#[derive(Debug, Clone, PartialEq, Eq)] 187#[derive(Debug, Clone, PartialEq, Eq)]
@@ -160,8 +202,10 @@ impl Attr {
160 fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> { 202 fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
161 let path = ModPath::from_src(ast.path()?, hygiene)?; 203 let path = ModPath::from_src(ast.path()?, hygiene)?;
162 let input = if let Some(lit) = ast.literal() { 204 let input = if let Some(lit) = ast.literal() {
163 // FIXME: escape? raw string? 205 let value = match lit.kind() {
164 let value = lit.syntax().first_token()?.text().trim_matches('"').into(); 206 ast::LiteralKind::String(string) => string.value()?.into(),
207 _ => lit.syntax().first_token()?.text().trim_matches('"').into(),
208 };
165 Some(AttrInput::Literal(value)) 209 Some(AttrInput::Literal(value))
166 } else if let Some(tt) = ast.token_tree() { 210 } else if let Some(tt) = ast.token_tree() {
167 Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0)) 211 Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
diff --git a/crates/hir_def/src/db.rs b/crates/hir_def/src/db.rs
index 6d694de11..7f250da33 100644
--- a/crates/hir_def/src/db.rs
+++ b/crates/hir_def/src/db.rs
@@ -10,7 +10,6 @@ use crate::{
10 attr::Attrs, 10 attr::Attrs,
11 body::{scope::ExprScopes, Body, BodySourceMap}, 11 body::{scope::ExprScopes, Body, BodySourceMap},
12 data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData}, 12 data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData},
13 docs::Documentation,
14 generics::GenericParams, 13 generics::GenericParams,
15 import_map::ImportMap, 14 import_map::ImportMap,
16 item_tree::ItemTree, 15 item_tree::ItemTree,
@@ -105,11 +104,6 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> {
105 #[salsa::invoke(LangItems::lang_item_query)] 104 #[salsa::invoke(LangItems::lang_item_query)]
106 fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>; 105 fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>;
107 106
108 // FIXME(https://github.com/rust-analyzer/rust-analyzer/issues/2148#issuecomment-550519102)
109 // Remove this query completely, in favor of `Attrs::docs` method
110 #[salsa::invoke(Documentation::documentation_query)]
111 fn documentation(&self, def: AttrDefId) -> Option<Documentation>;
112
113 #[salsa::invoke(ImportMap::import_map_query)] 107 #[salsa::invoke(ImportMap::import_map_query)]
114 fn import_map(&self, krate: CrateId) -> Arc<ImportMap>; 108 fn import_map(&self, krate: CrateId) -> Arc<ImportMap>;
115} 109}
diff --git a/crates/hir_def/src/docs.rs b/crates/hir_def/src/docs.rs
deleted file mode 100644
index 3e59a8f47..000000000
--- a/crates/hir_def/src/docs.rs
+++ /dev/null
@@ -1,121 +0,0 @@
1//! Defines hir documentation.
2//!
3//! This really shouldn't exist, instead, we should deshugar doc comments into attributes, see
4//! https://github.com/rust-analyzer/rust-analyzer/issues/2148#issuecomment-550519102
5
6use std::sync::Arc;
7
8use either::Either;
9use itertools::Itertools;
10use syntax::{ast, SmolStr};
11
12use crate::{
13 db::DefDatabase,
14 src::{HasChildSource, HasSource},
15 AdtId, AttrDefId, Lookup,
16};
17
18/// Holds documentation
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Documentation(Arc<str>);
21
22impl Into<String> for Documentation {
23 fn into(self) -> String {
24 self.as_str().to_owned()
25 }
26}
27
28impl Documentation {
29 fn new(s: &str) -> Documentation {
30 Documentation(s.into())
31 }
32
33 pub fn from_ast<N>(node: &N) -> Option<Documentation>
34 where
35 N: ast::DocCommentsOwner + ast::AttrsOwner,
36 {
37 docs_from_ast(node)
38 }
39
40 pub fn as_str(&self) -> &str {
41 &*self.0
42 }
43
44 pub(crate) fn documentation_query(
45 db: &dyn DefDatabase,
46 def: AttrDefId,
47 ) -> Option<Documentation> {
48 match def {
49 AttrDefId::ModuleId(module) => {
50 let def_map = db.crate_def_map(module.krate);
51 let src = def_map[module.local_id].declaration_source(db)?;
52 docs_from_ast(&src.value)
53 }
54 AttrDefId::FieldId(it) => {
55 let src = it.parent.child_source(db);
56 match &src.value[it.local_id] {
57 Either::Left(_tuple) => None,
58 Either::Right(record) => docs_from_ast(record),
59 }
60 }
61 AttrDefId::AdtId(it) => match it {
62 AdtId::StructId(it) => docs_from_ast(&it.lookup(db).source(db).value),
63 AdtId::EnumId(it) => docs_from_ast(&it.lookup(db).source(db).value),
64 AdtId::UnionId(it) => docs_from_ast(&it.lookup(db).source(db).value),
65 },
66 AttrDefId::EnumVariantId(it) => {
67 let src = it.parent.child_source(db);
68 docs_from_ast(&src.value[it.local_id])
69 }
70 AttrDefId::TraitId(it) => docs_from_ast(&it.lookup(db).source(db).value),
71 AttrDefId::MacroDefId(it) => docs_from_ast(&it.ast_id?.to_node(db.upcast())),
72 AttrDefId::ConstId(it) => docs_from_ast(&it.lookup(db).source(db).value),
73 AttrDefId::StaticId(it) => docs_from_ast(&it.lookup(db).source(db).value),
74 AttrDefId::FunctionId(it) => docs_from_ast(&it.lookup(db).source(db).value),
75 AttrDefId::TypeAliasId(it) => docs_from_ast(&it.lookup(db).source(db).value),
76 AttrDefId::ImplId(_) => None,
77 }
78 }
79}
80
81pub(crate) fn docs_from_ast<N>(node: &N) -> Option<Documentation>
82where
83 N: ast::DocCommentsOwner + ast::AttrsOwner,
84{
85 let doc_comment_text = node.doc_comment_text();
86 let doc_attr_text = expand_doc_attrs(node);
87 let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text);
88 docs.map(|it| Documentation::new(&it))
89}
90
91fn merge_doc_comments_and_attrs(
92 doc_comment_text: Option<String>,
93 doc_attr_text: Option<String>,
94) -> Option<String> {
95 match (doc_comment_text, doc_attr_text) {
96 (Some(mut comment_text), Some(attr_text)) => {
97 comment_text.push_str("\n");
98 comment_text.push_str(&attr_text);
99 Some(comment_text)
100 }
101 (Some(comment_text), None) => Some(comment_text),
102 (None, Some(attr_text)) => Some(attr_text),
103 (None, None) => None,
104 }
105}
106
107fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> {
108 let mut docs = String::new();
109 owner
110 .attrs()
111 .filter_map(|attr| attr.as_simple_key_value().filter(|(key, _)| key == "doc"))
112 .map(|(_, value)| value)
113 .intersperse(SmolStr::new_inline("\n"))
114 // No FromIterator<SmolStr> for String
115 .for_each(|s| docs.push_str(s.as_str()));
116 if docs.is_empty() {
117 None
118 } else {
119 Some(docs)
120 }
121}
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index b41c5acb2..02ed30e4d 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -31,7 +31,6 @@ pub mod adt;
31pub mod data; 31pub mod data;
32pub mod generics; 32pub mod generics;
33pub mod lang_item; 33pub mod lang_item;
34pub mod docs;
35 34
36pub mod expr; 35pub mod expr;
37pub mod body; 36pub mod body;
diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs
index ef6f85e15..e80b593aa 100644
--- a/crates/hir_def/src/nameres/tests/mod_resolution.rs
+++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs
@@ -372,7 +372,7 @@ fn module_resolution_explicit_path_mod_rs_with_win_separator() {
372 check( 372 check(
373 r#" 373 r#"
374//- /main.rs 374//- /main.rs
375#[path = "module\bar\mod.rs"] 375#[path = r"module\bar\mod.rs"]
376mod foo; 376mod foo;
377 377
378//- /module/bar/mod.rs 378//- /module/bar/mod.rs
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index dc9621f46..1b6ff6d21 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1,6 +1,6 @@
1use hir::{ 1use hir::{
2 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, 2 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasAttrs, HasSource, HirDisplay, Module,
3 Module, ModuleDef, ModuleSource, Semantics, 3 ModuleDef, ModuleSource, Semantics,
4}; 4};
5use ide_db::base_db::SourceDatabase; 5use ide_db::base_db::SourceDatabase;
6use ide_db::{ 6use ide_db::{
@@ -319,31 +319,27 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
319 let mod_path = definition_mod_path(db, &def); 319 let mod_path = definition_mod_path(db, &def);
320 return match def { 320 return match def {
321 Definition::Macro(it) => { 321 Definition::Macro(it) => {
322 let src = it.source(db); 322 let label = macro_label(&it.source(db).value);
323 let docs = Documentation::from_ast(&src.value).map(Into::into); 323 from_def_source_labeled(db, it, Some(label), mod_path)
324 hover_markup(docs, Some(macro_label(&src.value)), mod_path)
325 } 324 }
326 Definition::Field(it) => { 325 Definition::Field(def) => {
327 let src = it.source(db); 326 let src = def.source(db).value;
328 match src.value { 327 if let FieldSource::Named(it) = src {
329 FieldSource::Named(it) => { 328 from_def_source_labeled(db, def, it.short_label(), mod_path)
330 let docs = Documentation::from_ast(&it).map(Into::into); 329 } else {
331 hover_markup(docs, it.short_label(), mod_path) 330 None
332 }
333 _ => None,
334 } 331 }
335 } 332 }
336 Definition::ModuleDef(it) => match it { 333 Definition::ModuleDef(it) => match it {
337 ModuleDef::Module(it) => match it.definition_source(db).value { 334 ModuleDef::Module(it) => from_def_source_labeled(
338 ModuleSource::Module(it) => { 335 db,
339 let docs = Documentation::from_ast(&it).map(Into::into); 336 it,
340 hover_markup(docs, it.short_label(), mod_path) 337 match it.definition_source(db).value {
341 } 338 ModuleSource::Module(it) => it.short_label(),
342 ModuleSource::SourceFile(it) => { 339 ModuleSource::SourceFile(it) => it.short_label(),
343 let docs = Documentation::from_ast(&it).map(Into::into); 340 },
344 hover_markup(docs, it.short_label(), mod_path) 341 mod_path,
345 } 342 ),
346 },
347 ModuleDef::Function(it) => from_def_source(db, it, mod_path), 343 ModuleDef::Function(it) => from_def_source(db, it, mod_path),
348 ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path), 344 ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path),
349 ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path), 345 ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path),
@@ -371,12 +367,24 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
371 367
372 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup> 368 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
373 where 369 where
374 D: HasSource<Ast = A>, 370 D: HasSource<Ast = A> + HasAttrs + Copy,
375 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, 371 A: ShortLabel,
372 {
373 let short_label = def.source(db).value.short_label();
374 from_def_source_labeled(db, def, short_label, mod_path)
375 }
376
377 fn from_def_source_labeled<D>(
378 db: &RootDatabase,
379 def: D,
380 short_label: Option<String>,
381 mod_path: Option<String>,
382 ) -> Option<Markup>
383 where
384 D: HasAttrs,
376 { 385 {
377 let src = def.source(db); 386 let docs = def.attrs(db).docs().map(Into::into);
378 let docs = Documentation::from_ast(&src.value).map(Into::into); 387 hover_markup(docs, short_label, mod_path)
379 hover_markup(docs, src.value.short_label(), mod_path)
380 } 388 }
381} 389}
382 390
diff --git a/crates/ide_db/src/apply_change.rs b/crates/ide_db/src/apply_change.rs
index 987191fe3..e2251f2b7 100644
--- a/crates/ide_db/src/apply_change.rs
+++ b/crates/ide_db/src/apply_change.rs
@@ -166,7 +166,6 @@ impl RootDatabase {
166 hir::db::ModuleLangItemsQuery 166 hir::db::ModuleLangItemsQuery
167 hir::db::CrateLangItemsQuery 167 hir::db::CrateLangItemsQuery
168 hir::db::LangItemQuery 168 hir::db::LangItemQuery
169 hir::db::DocumentationQuery
170 hir::db::ImportMapQuery 169 hir::db::ImportMapQuery
171 170
172 // HirDatabase 171 // HirDatabase
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index fa40e64e8..a10b14778 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -24,6 +24,28 @@ impl ast::Comment {
24 .unwrap(); 24 .unwrap();
25 prefix 25 prefix
26 } 26 }
27
28 /// Returns the textual content of a doc comment block as a single string.
29 /// That is, strips leading `///` (+ optional 1 character of whitespace),
30 /// trailing `*/`, trailing whitespace and then joins the lines.
31 pub fn doc_comment(&self) -> Option<&str> {
32 let kind = self.kind();
33 match kind {
34 CommentKind { shape, doc: Some(_) } => {
35 let prefix = kind.prefix();
36 let text = &self.text().as_str()[prefix.len()..];
37 let ws = text.chars().next().filter(|c| c.is_whitespace());
38 let text = ws.map_or(text, |ws| &text[ws.len_utf8()..]);
39 match shape {
40 CommentShape::Block if text.ends_with("*/") => {
41 Some(&text[..text.len() - "*/".len()])
42 }
43 _ => Some(text),
44 }
45 }
46 _ => None,
47 }
48 }
27} 49}
28 50
29#[derive(Debug, PartialEq, Eq, Clone, Copy)] 51#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -73,6 +95,11 @@ impl CommentKind {
73 .unwrap(); 95 .unwrap();
74 kind 96 kind
75 } 97 }
98
99 fn prefix(&self) -> &'static str {
100 let &(prefix, _) = CommentKind::BY_PREFIX.iter().find(|(_, kind)| kind == self).unwrap();
101 prefix
102 }
76} 103}
77 104
78impl ast::Whitespace { 105impl ast::Whitespace {
diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs
index 0bdc22d95..13a769d51 100644
--- a/crates/syntax/src/ast/traits.rs
+++ b/crates/syntax/src/ast/traits.rs
@@ -91,40 +91,12 @@ impl CommentIter {
91 /// That is, strips leading `///` (+ optional 1 character of whitespace), 91 /// That is, strips leading `///` (+ optional 1 character of whitespace),
92 /// trailing `*/`, trailing whitespace and then joins the lines. 92 /// trailing `*/`, trailing whitespace and then joins the lines.
93 pub fn doc_comment_text(self) -> Option<String> { 93 pub fn doc_comment_text(self) -> Option<String> {
94 let mut has_comments = false; 94 let docs =
95 let docs = self 95 self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)).join("\n");
96 .filter(|comment| comment.kind().doc.is_some()) 96 if docs.is_empty() {
97 .map(|comment| {
98 has_comments = true;
99 let prefix_len = comment.prefix().len();
100
101 let line: &str = comment.text().as_str();
102
103 // Determine if the prefix or prefix + 1 char is stripped
104 let pos =
105 if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) {
106 prefix_len + ws.len_utf8()
107 } else {
108 prefix_len
109 };
110
111 let end = if comment.kind().shape.is_block() && line.ends_with("*/") {
112 line.len() - 2
113 } else {
114 line.len()
115 };
116
117 // Note that we do not trim the end of the line here
118 // since whitespace can have special meaning at the end
119 // of a line in markdown.
120 line[pos..end].to_owned()
121 })
122 .join("\n");
123
124 if has_comments {
125 Some(docs)
126 } else {
127 None 97 None
98 } else {
99 Some(docs)
128 } 100 }
129 } 101 }
130} 102}