mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-29 06:23:25 +00:00
Auto merge of #17024 - roife:fix-issue-16980, r=Veykril
fix: handle escaped chars in doc comments fix #16980. For `ast::LiteralKind::String`, store the original string value.
This commit is contained in:
commit
05428c5640
5 changed files with 68 additions and 14 deletions
|
@ -5,7 +5,7 @@ pub mod builtin;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use std::{hash::Hash, ops, slice::Iter as SliceIter};
|
use std::{borrow::Cow, hash::Hash, ops, slice::Iter as SliceIter};
|
||||||
|
|
||||||
use base_db::CrateId;
|
use base_db::CrateId;
|
||||||
use cfg::{CfgExpr, CfgOptions};
|
use cfg::{CfgExpr, CfgOptions};
|
||||||
|
@ -573,6 +573,10 @@ impl<'attr> AttrQuery<'attr> {
|
||||||
self.attrs().find_map(|attr| attr.string_value())
|
self.attrs().find_map(|attr| attr.string_value())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_value_unescape(self) -> Option<Cow<'attr, str>> {
|
||||||
|
self.attrs().find_map(|attr| attr.string_value_unescape())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn exists(self) -> bool {
|
pub fn exists(self) -> bool {
|
||||||
self.attrs().next().is_some()
|
self.attrs().next().is_some()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1917,7 +1917,7 @@ impl ModCollector<'_, '_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_module(&mut self, module_id: FileItemTreeId<Mod>, attrs: &Attrs) {
|
fn collect_module(&mut self, module_id: FileItemTreeId<Mod>, attrs: &Attrs) {
|
||||||
let path_attr = attrs.by_key("path").string_value();
|
let path_attr = attrs.by_key("path").string_value_unescape();
|
||||||
let is_macro_use = attrs.by_key("macro_use").exists();
|
let is_macro_use = attrs.by_key("macro_use").exists();
|
||||||
let module = &self.item_tree[module_id];
|
let module = &self.item_tree[module_id];
|
||||||
match &module.kind {
|
match &module.kind {
|
||||||
|
@ -1931,7 +1931,8 @@ impl ModCollector<'_, '_> {
|
||||||
module_id,
|
module_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
let Some(mod_dir) = self.mod_dir.descend_into_definition(&module.name, path_attr)
|
let Some(mod_dir) =
|
||||||
|
self.mod_dir.descend_into_definition(&module.name, path_attr.as_deref())
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -1952,8 +1953,12 @@ impl ModCollector<'_, '_> {
|
||||||
ModKind::Outline => {
|
ModKind::Outline => {
|
||||||
let ast_id = AstId::new(self.file_id(), module.ast_id);
|
let ast_id = AstId::new(self.file_id(), module.ast_id);
|
||||||
let db = self.def_collector.db;
|
let db = self.def_collector.db;
|
||||||
match self.mod_dir.resolve_declaration(db, self.file_id(), &module.name, path_attr)
|
match self.mod_dir.resolve_declaration(
|
||||||
{
|
db,
|
||||||
|
self.file_id(),
|
||||||
|
&module.name,
|
||||||
|
path_attr.as_deref(),
|
||||||
|
) {
|
||||||
Ok((file_id, is_mod_rs, mod_dir)) => {
|
Ok((file_id, is_mod_rs, mod_dir)) => {
|
||||||
let item_tree = db.file_item_tree(file_id.into());
|
let item_tree = db.file_item_tree(file_id.into());
|
||||||
let krate = self.def_collector.def_map.krate;
|
let krate = self.def_collector.def_map.krate;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
||||||
use std::{fmt, ops};
|
use std::{borrow::Cow, fmt, ops};
|
||||||
|
|
||||||
use base_db::CrateId;
|
use base_db::CrateId;
|
||||||
use cfg::CfgExpr;
|
use cfg::CfgExpr;
|
||||||
|
@ -8,6 +8,7 @@ use intern::Interned;
|
||||||
use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
|
use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use span::{Span, SyntaxContextId};
|
use span::{Span, SyntaxContextId};
|
||||||
|
use syntax::unescape;
|
||||||
use syntax::{ast, format_smolstr, match_ast, AstNode, AstToken, SmolStr, SyntaxNode};
|
use syntax::{ast, format_smolstr, match_ast, AstNode, AstToken, SmolStr, SyntaxNode};
|
||||||
use triomphe::ThinArc;
|
use triomphe::ThinArc;
|
||||||
|
|
||||||
|
@ -54,8 +55,7 @@ impl RawAttrs {
|
||||||
Attr {
|
Attr {
|
||||||
id,
|
id,
|
||||||
input: Some(Interned::new(AttrInput::Literal(tt::Literal {
|
input: Some(Interned::new(AttrInput::Literal(tt::Literal {
|
||||||
// FIXME: Escape quotes from comment content
|
text: SmolStr::new(format_smolstr!("\"{}\"", Self::escape_chars(doc))),
|
||||||
text: SmolStr::new(format_smolstr!("\"{doc}\"",)),
|
|
||||||
span,
|
span,
|
||||||
}))),
|
}))),
|
||||||
path: Interned::new(ModPath::from(crate::name!(doc))),
|
path: Interned::new(ModPath::from(crate::name!(doc))),
|
||||||
|
@ -74,6 +74,10 @@ impl RawAttrs {
|
||||||
RawAttrs { entries }
|
RawAttrs { entries }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn escape_chars(s: &str) -> String {
|
||||||
|
s.replace('\\', r#"\\"#).replace('"', r#"\""#)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_attrs_owner(
|
pub fn from_attrs_owner(
|
||||||
db: &dyn ExpandDatabase,
|
db: &dyn ExpandDatabase,
|
||||||
owner: InFile<&dyn ast::HasAttrs>,
|
owner: InFile<&dyn ast::HasAttrs>,
|
||||||
|
@ -297,6 +301,18 @@ impl Attr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_value_unescape(&self) -> Option<Cow<'_, str>> {
|
||||||
|
match self.input.as_deref()? {
|
||||||
|
AttrInput::Literal(it) => match it.text.strip_prefix('r') {
|
||||||
|
Some(it) => {
|
||||||
|
it.trim_matches('#').strip_prefix('"')?.strip_suffix('"').map(Cow::Borrowed)
|
||||||
|
}
|
||||||
|
None => it.text.strip_prefix('"')?.strip_suffix('"').and_then(unescape),
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// #[path(ident)]
|
/// #[path(ident)]
|
||||||
pub fn single_ident_value(&self) -> Option<&tt::Ident> {
|
pub fn single_ident_value(&self) -> Option<&tt::Ident> {
|
||||||
match self.input.as_deref()? {
|
match self.input.as_deref()? {
|
||||||
|
@ -346,6 +362,33 @@ impl Attr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unescape(s: &str) -> Option<Cow<'_, str>> {
|
||||||
|
let mut buf = String::new();
|
||||||
|
let mut prev_end = 0;
|
||||||
|
let mut has_error = false;
|
||||||
|
unescape::unescape_unicode(s, unescape::Mode::Str, &mut |char_range, unescaped_char| match (
|
||||||
|
unescaped_char,
|
||||||
|
buf.capacity() == 0,
|
||||||
|
) {
|
||||||
|
(Ok(c), false) => buf.push(c),
|
||||||
|
(Ok(_), true) if char_range.len() == 1 && char_range.start == prev_end => {
|
||||||
|
prev_end = char_range.end
|
||||||
|
}
|
||||||
|
(Ok(c), true) => {
|
||||||
|
buf.reserve_exact(s.len());
|
||||||
|
buf.push_str(&s[..prev_end]);
|
||||||
|
buf.push(c);
|
||||||
|
}
|
||||||
|
(Err(_), _) => has_error = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
match (has_error, buf.capacity() == 0) {
|
||||||
|
(true, _) => None,
|
||||||
|
(false, false) => Some(Cow::Owned(buf)),
|
||||||
|
(false, true) => Some(Cow::Borrowed(s)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn collect_attrs(
|
pub fn collect_attrs(
|
||||||
owner: &dyn ast::HasAttrs,
|
owner: &dyn ast::HasAttrs,
|
||||||
) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
|
) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
|
||||||
|
|
|
@ -91,8 +91,10 @@ pub fn docs_with_rangemap(
|
||||||
db: &dyn DefDatabase,
|
db: &dyn DefDatabase,
|
||||||
attrs: &AttrsWithOwner,
|
attrs: &AttrsWithOwner,
|
||||||
) -> Option<(Documentation, DocsRangeMap)> {
|
) -> Option<(Documentation, DocsRangeMap)> {
|
||||||
let docs =
|
let docs = attrs
|
||||||
attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id)));
|
.by_key("doc")
|
||||||
|
.attrs()
|
||||||
|
.filter_map(|attr| attr.string_value_unescape().map(|s| (s, attr.id)));
|
||||||
let indent = doc_indent(attrs);
|
let indent = doc_indent(attrs);
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
let mut mapping = Vec::new();
|
let mut mapping = Vec::new();
|
||||||
|
@ -132,7 +134,7 @@ pub fn docs_with_rangemap(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn docs_from_attrs(attrs: &hir::Attrs) -> Option<String> {
|
pub fn docs_from_attrs(attrs: &hir::Attrs) -> Option<String> {
|
||||||
let docs = attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value());
|
let docs = attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value_unescape());
|
||||||
let indent = doc_indent(attrs);
|
let indent = doc_indent(attrs);
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
for doc in docs {
|
for doc in docs {
|
||||||
|
@ -270,10 +272,9 @@ fn doc_indent(attrs: &hir::Attrs) -> usize {
|
||||||
attrs
|
attrs
|
||||||
.by_key("doc")
|
.by_key("doc")
|
||||||
.attrs()
|
.attrs()
|
||||||
.filter_map(|attr| attr.string_value())
|
.filter_map(|attr| attr.string_value()) // no need to use unescape version here
|
||||||
.flat_map(|s| s.lines())
|
.flat_map(|s| s.lines())
|
||||||
.filter(|line| !line.chars().all(|c| c.is_whitespace()))
|
.filter_map(|line| line.chars().position(|c| !c.is_whitespace()))
|
||||||
.map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
|
|
||||||
.min()
|
.min()
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ pub use rowan::{
|
||||||
api::Preorder, Direction, GreenNode, NodeOrToken, SyntaxText, TextRange, TextSize,
|
api::Preorder, Direction, GreenNode, NodeOrToken, SyntaxText, TextRange, TextSize,
|
||||||
TokenAtOffset, WalkEvent,
|
TokenAtOffset, WalkEvent,
|
||||||
};
|
};
|
||||||
|
pub use rustc_lexer::unescape;
|
||||||
pub use smol_str::{format_smolstr, SmolStr};
|
pub use smol_str::{format_smolstr, SmolStr};
|
||||||
|
|
||||||
/// `Parse` is the result of the parsing: a syntax tree and a collection of
|
/// `Parse` is the result of the parsing: a syntax tree and a collection of
|
||||||
|
|
Loading…
Reference in a new issue