Repository: rust-analyzer/rust-analyzer (rust)

Issue:
Doc/Attribute range mapping is broken with inner file docs + outer impl docs
Pasting the following snippet in a file causes the highlighting to break
```rust
//! This is a module to test doc injection.
//! ```
//! fn test() {}
//! ```

/// This is an impl with a code block.
///
/// ```
/// fn foo() {
///
/// }
/// ```
impl () {}
```
![image](https://user-images.githubusercontent.com/3757771/118365744-6b06cc80-b59e-11eb-83ac-d135a39add7d.png)

This only seems to happen for when the annotated item is an `impl`, another requirement is that number of lines of doc comments on the `impl` have to be at least 3 more than the inner doc comments. This also breaks hovering on intra doc links so this seems to be a problem with the source mapping?

This `impl` stuff seems so oddly specific ...

Pull request:
Attach comments to ast::Impl
bors r+
Fixes #8847

Patch:
diff --git a/crates/ide/src/matching_brace.rs b/crates/ide/src/matching_brace.rs
index 261dcc2559..011c8cc553 100644
--- a/crates/ide/src/matching_brace.rs
+++ b/crates/ide/src/matching_brace.rs
@@ -19,14 +19,10 @@ use syntax::{
 pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
     const BRACES: &[SyntaxKind] =
         &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]];
-    let (brace_token, brace_idx) = file
-        .syntax()
-        .token_at_offset(offset)
-        .filter_map(|node| {
-            let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
-            Some((node, idx))
-        })
-        .next()?;
+    let (brace_token, brace_idx) = file.syntax().token_at_offset(offset).find_map(|node| {
+        let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
+        Some((node, idx))
+    })?;
     let parent = brace_token.parent()?;
     if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) {
         cov_mark::hit!(pipes_not_braces);
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index 99365c8a73..9b1f480446 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -1,6 +1,8 @@
 use hir::Semantics;
-use ide_db::base_db::{CrateId, FileId, FilePosition};
-use ide_db::RootDatabase;
+use ide_db::{
+    base_db::{CrateId, FileId, FilePosition},
+    RootDatabase,
+};
 use itertools::Itertools;
 use syntax::{
     algo::find_node_at_offset,
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs
index 61b6671044..b635e0ca35 100644
--- a/crates/ide_completion/src/completions/keyword.rs
+++ b/crates/ide_completion/src/completions/keyword.rs
@@ -9,21 +9,22 @@ use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKin
 pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) {
     // complete keyword "crate" in use stmt
     let source_range = ctx.source_range();
-    let kw_completion = move |text: &str| {
-        let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, text);
-        item.kind(CompletionItemKind::Keyword).insert_text(text);
-        item
-    };
 
     if ctx.use_item_syntax.is_some() {
         if ctx.path_qual.is_none() {
-            kw_completion("crate::").add_to(acc);
+            let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, "crate::");
+            item.kind(CompletionItemKind::Keyword).insert_text("crate::");
+            item.add_to(acc);
         }
-        kw_completion("self").add_to(acc);
+        let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, "self");
+        item.kind(CompletionItemKind::Keyword);
+        item.add_to(acc);
         if iter::successors(ctx.path_qual.clone(), |p| p.qualifier())
             .all(|p| p.segment().and_then(|s| s.super_token()).is_some())
         {
-            kw_completion("super::").add_to(acc);
+            let mut item = CompletionItem::new(CompletionKind::Keyword, source_range, "super::");
+            item.kind(CompletionItemKind::Keyword).insert_text("super::");
+            item.add_to(acc);
         }
     }
 
@@ -31,8 +32,9 @@ pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC
     if let Some(receiver) = &ctx.dot_receiver {
         if let Some(ty) = ctx.sema.type_of_expr(receiver) {
             if ty.impls_future(ctx.db) {
-                let mut item = kw_completion("await");
-                item.detail("expr.await");
+                let mut item =
+                    CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await");
+                item.kind(CompletionItemKind::Keyword).detail("expr.await").insert_text("await");
                 item.add_to(acc);
             }
         };
diff --git a/crates/syntax/src/parsing/text_tree_sink.rs b/crates/syntax/src/parsing/text_tree_sink.rs
index 1934204eab..d63ec080b4 100644
--- a/crates/syntax/src/parsing/text_tree_sink.rs
+++ b/crates/syntax/src/parsing/text_tree_sink.rs
@@ -147,8 +147,8 @@ fn n_attached_trivias<'a>(
     trivias: impl Iterator<Item = (SyntaxKind, &'a str)>,
 ) -> usize {
     match kind {
-        MACRO_CALL | MACRO_RULES | MACRO_DEF | CONST | TYPE_ALIAS | STRUCT | UNION | ENUM
-        | VARIANT | FN | TRAIT | MODULE | RECORD_FIELD | STATIC | USE => {
+        CONST | ENUM | FN | IMPL | MACRO_CALL | MACRO_DEF | MACRO_RULES | MODULE | RECORD_FIELD
+        | STATIC | STRUCT | TRAIT | TUPLE_FIELD | TYPE_ALIAS | UNION | USE | VARIANT => {
             let mut res = 0;
             let mut trivias = trivias.enumerate().peekable();
 


Test patch:
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 8d83ba2065..921a956e66 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -37,13 +37,25 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 .unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
 </style>
-<pre><code><span class="comment documentation">/// ```</span>
+<pre><code><span class="comment documentation">//! This is a module to test doc injection.</span>
+<span class="comment documentation">//! ```</span>
+<span class="comment documentation">//! </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">test</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span><span class="brace injected">}</span>
+<span class="comment documentation">//! ```</span>
+
+<span class="comment documentation">/// ```</span>
 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"early doctests should not go boom"</span><span class="semicolon injected">;</span>
 <span class="comment documentation">/// ```</span>
 <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="brace">{</span>
     <span class="field declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span><span class="comma">,</span>
 <span class="brace">}</span>
 
+<span class="comment documentation">/// This is an impl with a code block.</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// ```</span>
+<span class="comment documentation">/// </span><span class="keyword injected">fn</span><span class="none injected"> </span><span class="function declaration injected">foo</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="none injected"> </span><span class="brace injected">{</span>
+<span class="comment documentation">///</span>
+<span class="comment documentation">/// </span><span class="brace injected">}</span>
+<span class="comment documentation">/// ```</span>
 <span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
     <span class="comment documentation">/// ```</span>
     <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="punctuation injected">_</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Call me</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 32f2d9038e..95408dfb2e 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -524,6 +524,11 @@ fn main() {
 fn test_highlight_doc_comment() {
     check_highlighting(
         r#"
+//! This is a module to test doc injection.
+//! ```
+//! fn test() {}
+//! ```
+
 /// ```
 /// let _ = "early doctests should not go boom";
 /// ```
@@ -531,6 +536,13 @@ struct Foo {
     bar: bool,
 }
 
+/// This is an impl with a code block.
+///
+/// ```
+/// fn foo() {
+///
+/// }
+/// ```
 impl Foo {
     /// ```
     /// let _ = "Call me
diff --git a/crates/syntax/test_data/parser/ok/0045_block_attrs.rast b/crates/syntax/test_data/parser/ok/0045_block_attrs.rast
index 50ab52d325..5e50b4e0bc 100644
--- a/crates/syntax/test_data/parser/ok/0045_block_attrs.rast
+++ b/crates/syntax/test_data/parser/ok/0045_block_attrs.rast
@@ -127,9 +127,9 @@ SOURCE_FILE@0..764
       WHITESPACE@537..538 "\n"
       R_CURLY@538..539 "}"
   WHITESPACE@539..541 "\n\n"
-  COMMENT@541..601 "// https://github.com ..."
-  WHITESPACE@601..602 "\n"
-  IMPL@602..763
+  IMPL@541..763
+    COMMENT@541..601 "// https://github.com ..."
+    WHITESPACE@601..602 "\n"
     IMPL_KW@602..606 "impl"
     WHITESPACE@606..607 " "
     PATH_TYPE@607..615