Skip to content

Commit b064f6d

Browse files
committed
Keep doc attribute order
1 parent efe86a4 commit b064f6d

File tree

3 files changed

+61
-55
lines changed

3 files changed

+61
-55
lines changed

crates/hir_def/src/attr.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use itertools::Itertools;
99
use mbe::ast_to_token_tree;
1010
use syntax::{
1111
ast::{self, AstNode, AttrsOwner},
12-
SmolStr,
12+
AstToken, SmolStr,
1313
};
1414
use tt::Subtree;
1515

@@ -110,18 +110,25 @@ impl Attrs {
110110
}
111111

112112
pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
113-
let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map(
114-
|docs_text| Attr {
115-
input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
116-
path: ModPath::from(hir_expand::name!(doc)),
117-
},
118-
);
119-
let mut attrs = owner.attrs().peekable();
120-
let entries = if attrs.peek().is_none() && docs.is_none() {
113+
let docs = ast::CommentIter::from_syntax_node(owner.syntax()).map(|docs_text| {
114+
(
115+
docs_text.syntax().text_range().start(),
116+
docs_text.doc_comment().map(|doc| Attr {
117+
input: Some(AttrInput::Literal(SmolStr::new(doc))),
118+
path: ModPath::from(hir_expand::name!(doc)),
119+
}),
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() {
121128
// Avoid heap allocation
122129
None
123130
} else {
124-
Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
131+
Some(attrs.into_iter().flat_map(|(_, attr)| attr).collect())
125132
};
126133
Attrs { entries }
127134
}
@@ -195,10 +202,15 @@ impl Attr {
195202
fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
196203
let path = ModPath::from_src(ast.path()?, hygiene)?;
197204
let input = if let Some(lit) = ast.literal() {
198-
let value = if let ast::LiteralKind::String(string) = lit.kind() {
199-
string.value()?.into()
200-
} else {
201-
lit.syntax().first_token()?.text().trim_matches('"').into()
205+
// FIXME: escape?
206+
let value = match lit.kind() {
207+
ast::LiteralKind::String(string) if string.is_raw() => {
208+
let text = string.text().as_str();
209+
let text = &text[string.text_range_between_quotes()?
210+
- string.syntax().text_range().start()];
211+
text.into()
212+
}
213+
_ => lit.syntax().first_token()?.text().trim_matches('"').into(),
202214
};
203215
Some(AttrInput::Literal(value))
204216
} else if let Some(tt) = ast.token_tree() {

crates/syntax/src/ast/token_ext.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,33 @@ impl ast::Comment {
1818
}
1919

2020
pub fn prefix(&self) -> &'static str {
21-
let &(prefix, _kind) = CommentKind::BY_PREFIX
22-
.iter()
23-
.find(|&(prefix, kind)| self.kind() == *kind && self.text().starts_with(prefix))
24-
.unwrap();
21+
let &(prefix, _kind) = CommentKind::with_prefix_from_text(self.text());
2522
prefix
2623
}
24+
25+
pub fn kind_and_prefix(&self) -> &(&'static str, CommentKind) {
26+
CommentKind::with_prefix_from_text(self.text())
27+
}
28+
29+
/// Returns the textual content of a doc comment block as a single string.
30+
/// That is, strips leading `///` (+ optional 1 character of whitespace),
31+
/// trailing `*/`, trailing whitespace and then joins the lines.
32+
pub fn doc_comment(&self) -> Option<&str> {
33+
match self.kind_and_prefix() {
34+
(prefix, CommentKind { shape, doc: Some(_) }) => {
35+
let text = &self.text().as_str()[prefix.len()..];
36+
let ws = text.chars().next().filter(|c| c.is_whitespace());
37+
let text = ws.map_or(text, |ws| &text[ws.len_utf8()..]);
38+
match shape {
39+
CommentShape::Block if text.ends_with("*/") => {
40+
Some(&text[..text.len() - "*/".len()])
41+
}
42+
_ => Some(text),
43+
}
44+
}
45+
_ => None,
46+
}
47+
}
2748
}
2849

2950
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -67,12 +88,13 @@ impl CommentKind {
6788
];
6889

6990
pub(crate) fn from_text(text: &str) -> CommentKind {
70-
let &(_prefix, kind) = CommentKind::BY_PREFIX
71-
.iter()
72-
.find(|&(prefix, _kind)| text.starts_with(prefix))
73-
.unwrap();
91+
let &(_prefix, kind) = Self::with_prefix_from_text(text);
7492
kind
7593
}
94+
95+
fn with_prefix_from_text(text: &str) -> &(&'static str, CommentKind) {
96+
CommentKind::BY_PREFIX.iter().find(|&(prefix, _kind)| text.starts_with(prefix)).unwrap()
97+
}
7698
}
7799

78100
impl ast::Whitespace {

crates/syntax/src/ast/traits.rs

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -91,40 +91,12 @@ impl CommentIter {
9191
/// That is, strips leading `///` (+ optional 1 character of whitespace),
9292
/// trailing `*/`, trailing whitespace and then joins the lines.
9393
pub fn doc_comment_text(self) -> Option<String> {
94-
let mut has_comments = false;
95-
let docs = self
96-
.filter(|comment| comment.kind().doc.is_some())
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 {
94+
let docs =
95+
self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)).join("\n");
96+
if docs.is_empty() {
12797
None
98+
} else {
99+
Some(docs)
128100
}
129101
}
130102
}

0 commit comments

Comments
 (0)