aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def/src/attr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_def/src/attr.rs')
-rw-r--r--crates/hir_def/src/attr.rs68
1 files changed, 56 insertions, 12 deletions
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))