about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-04-19 13:19:22 +0000
committerbors <bors@rust-lang.org>2024-04-19 13:19:22 +0000
commit05428c56404a7751f3bd6cb61d739a8249f01ebc (patch)
tree7220bc8a88adbf6ce4971e99b8fb659afc5ae5fe
parent77ce2950065e83e25479a429280b0c0eea805b12 (diff)
parent3e232bb78a362cb33420c329e7d17438987a9fb9 (diff)
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.
-rw-r--r--crates/hir-def/src/attr.rs6
-rw-r--r--crates/hir-def/src/nameres/collector.rs13
-rw-r--r--crates/hir-expand/src/attrs.rs49
-rw-r--r--crates/ide-db/src/documentation.rs13
-rw-r--r--crates/syntax/src/lib.rs1
5 files changed, 68 insertions, 14 deletions
diff --git a/crates/hir-def/src/attr.rs b/crates/hir-def/src/attr.rs
index f4f78ce6a4d..d9eeffd7983 100644
--- a/crates/hir-def/src/attr.rs
+++ b/crates/hir-def/src/attr.rs
@@ -5,7 +5,7 @@ pub mod builtin;
 #[cfg(test)]
 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 cfg::{CfgExpr, CfgOptions};
@@ -573,6 +573,10 @@ impl<'attr> AttrQuery<'attr> {
         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 {
         self.attrs().next().is_some()
     }
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index 0a74123abbc..0a6cd0fe9ed 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -1917,7 +1917,7 @@ impl ModCollector<'_, '_> {
     }
 
     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 module = &self.item_tree[module_id];
         match &module.kind {
@@ -1931,7 +1931,8 @@ impl ModCollector<'_, '_> {
                     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 {
                     return;
                 };
@@ -1952,8 +1953,12 @@ impl ModCollector<'_, '_> {
             ModKind::Outline => {
                 let ast_id = AstId::new(self.file_id(), module.ast_id);
                 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)) => {
                         let item_tree = db.file_item_tree(file_id.into());
                         let krate = self.def_collector.def_map.krate;
diff --git a/crates/hir-expand/src/attrs.rs b/crates/hir-expand/src/attrs.rs
index f1540498f26..f8bf88d83cd 100644
--- a/crates/hir-expand/src/attrs.rs
+++ b/crates/hir-expand/src/attrs.rs
@@ -1,5 +1,5 @@
 //! 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 cfg::CfgExpr;
@@ -8,6 +8,7 @@ use intern::Interned;
 use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
 use smallvec::{smallvec, SmallVec};
 use span::{Span, SyntaxContextId};
+use syntax::unescape;
 use syntax::{ast, format_smolstr, match_ast, AstNode, AstToken, SmolStr, SyntaxNode};
 use triomphe::ThinArc;
 
@@ -54,8 +55,7 @@ impl RawAttrs {
                     Attr {
                         id,
                         input: Some(Interned::new(AttrInput::Literal(tt::Literal {
-                            // FIXME: Escape quotes from comment content
-                            text: SmolStr::new(format_smolstr!("\"{doc}\"",)),
+                            text: SmolStr::new(format_smolstr!("\"{}\"", Self::escape_chars(doc))),
                             span,
                         }))),
                         path: Interned::new(ModPath::from(crate::name!(doc))),
@@ -74,6 +74,10 @@ impl RawAttrs {
         RawAttrs { entries }
     }
 
+    fn escape_chars(s: &str) -> String {
+        s.replace('\\', r#"\\"#).replace('"', r#"\""#)
+    }
+
     pub fn from_attrs_owner(
         db: &dyn ExpandDatabase,
         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)]
     pub fn single_ident_value(&self) -> Option<&tt::Ident> {
         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(
     owner: &dyn ast::HasAttrs,
 ) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
diff --git a/crates/ide-db/src/documentation.rs b/crates/ide-db/src/documentation.rs
index 72ca354365e..58e77b95c32 100644
--- a/crates/ide-db/src/documentation.rs
+++ b/crates/ide-db/src/documentation.rs
@@ -91,8 +91,10 @@ pub fn docs_with_rangemap(
     db: &dyn DefDatabase,
     attrs: &AttrsWithOwner,
 ) -> Option<(Documentation, DocsRangeMap)> {
-    let docs =
-        attrs.by_key("doc").attrs().filter_map(|attr| attr.string_value().map(|s| (s, attr.id)));
+    let docs = attrs
+        .by_key("doc")
+        .attrs()
+        .filter_map(|attr| attr.string_value_unescape().map(|s| (s, attr.id)));
     let indent = doc_indent(attrs);
     let mut buf = String::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> {
-    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 mut buf = String::new();
     for doc in docs {
@@ -270,10 +272,9 @@ fn doc_indent(attrs: &hir::Attrs) -> usize {
     attrs
         .by_key("doc")
         .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())
-        .filter(|line| !line.chars().all(|c| c.is_whitespace()))
-        .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
+        .filter_map(|line| line.chars().position(|c| !c.is_whitespace()))
         .min()
         .unwrap_or(0)
 }
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index e7bbf936dc1..3a9ebafe87d 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -65,6 +65,7 @@ pub use rowan::{
     api::Preorder, Direction, GreenNode, NodeOrToken, SyntaxText, TextRange, TextSize,
     TokenAtOffset, WalkEvent,
 };
+pub use rustc_lexer::unescape;
 pub use smol_str::{format_smolstr, SmolStr};
 
 /// `Parse` is the result of the parsing: a syntax tree and a collection of