Repository: rust-analyzer/rust-analyzer
Pull request #8069 (2021-03-17 18:05:19)

Inject highlight into block doc comments
bors r+
Closes https://github.com/rust-analyzer/rust-analyzer/issues/6873

bors[bot]: Build failed:
  * [Rust (ubuntu-latest)](https://github.com/rust-analyzer/rust-analyzer/runs/2133355872)

Patch:
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index e976e419eb..1a27f7bf21 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -9,7 +9,7 @@ use crate::{
     attr::Attrs,
     body::Expander,
     db::DefDatabase,
-    item_tree::{AssocItem, FunctionQualifier, ItemTreeId, ModItem, Param},
+    item_tree::{AssocItem, FunctionQualifier, ItemTreeId, ModItem},
     type_ref::{TypeBound, TypeRef},
     visibility::RawVisibility,
     AssocContainerId, AssocItemId, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId,
@@ -36,38 +36,19 @@ impl FunctionData {
     pub(crate) fn fn_data_query(db: &dyn DefDatabase, func: FunctionId) -> Arc<FunctionData> {
         let loc = func.lookup(db);
         let krate = loc.container.module(db).krate;
-        let crate_graph = db.crate_graph();
-        let cfg_options = &crate_graph[krate].cfg_options;
         let item_tree = db.item_tree(loc.id.file_id);
         let func = &item_tree[loc.id.value];
 
-        let enabled_params = func
-            .params
-            .clone()
-            .filter(|&param| item_tree.attrs(db, krate, param.into()).is_cfg_enabled(cfg_options));
-
-        // If last cfg-enabled param is a `...` param, it's a varargs function.
-        let is_varargs = enabled_params
-            .clone()
-            .next_back()
-            .map_or(false, |param| matches!(item_tree[param], Param::Varargs));
-
         Arc::new(FunctionData {
             name: func.name.clone(),
-            params: enabled_params
-                .clone()
-                .filter_map(|id| match &item_tree[id] {
-                    Param::Normal(ty) => Some(item_tree[*ty].clone()),
-                    Param::Varargs => None,
-                })
-                .collect(),
+            params: func.params.iter().map(|id| item_tree[*id].clone()).collect(),
             ret_type: item_tree[func.ret_type].clone(),
             attrs: item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()),
             has_self_param: func.has_self_param,
             has_body: func.has_body,
             qualifier: func.qualifier.clone(),
             is_in_extern_block: func.is_in_extern_block,
-            is_varargs,
+            is_varargs: func.is_varargs,
             visibility: item_tree[func.visibility].clone(),
         })
     }
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 90df3d9291..7bb22c4c49 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -134,7 +134,6 @@ impl ItemTree {
                 imports,
                 extern_crates,
                 functions,
-                params,
                 structs,
                 fields,
                 unions,
@@ -158,7 +157,6 @@ impl ItemTree {
             imports.shrink_to_fit();
             extern_crates.shrink_to_fit();
             functions.shrink_to_fit();
-            params.shrink_to_fit();
             structs.shrink_to_fit();
             fields.shrink_to_fit();
             unions.shrink_to_fit();
@@ -305,7 +303,6 @@ struct ItemTreeData {
     imports: Arena<Import>,
     extern_crates: Arena<ExternCrate>,
     functions: Arena<Function>,
-    params: Arena<Param>,
     structs: Arena<Struct>,
     fields: Arena<Field>,
     unions: Arena<Union>,
@@ -337,7 +334,6 @@ pub enum AttrOwner {
 
     Variant(Idx<Variant>),
     Field(Idx<Field>),
-    Param(Idx<Param>),
 }
 
 macro_rules! from_attrs {
@@ -352,7 +348,7 @@ macro_rules! from_attrs {
     };
 }
 
-from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>), Param(Idx<Param>));
+from_attrs!(ModItem(ModItem), Variant(Idx<Variant>), Field(Idx<Field>));
 
 /// Trait implemented by all item nodes in the item tree.
 pub trait ItemTreeNode: Clone {
@@ -488,7 +484,7 @@ macro_rules! impl_index {
     };
 }
 
-impl_index!(fields: Field, variants: Variant, params: Param);
+impl_index!(fields: Field, variants: Variant);
 
 impl Index<RawVisibilityId> for ItemTree {
     type Output = RawVisibility;
@@ -564,17 +560,12 @@ pub struct Function {
     /// Whether the function is located in an `extern` block (*not* whether it is an
     /// `extern "abi" fn`).
     pub is_in_extern_block: bool,
-    pub params: IdRange<Param>,
+    pub params: Box<[Idx<TypeRef>]>,
+    pub is_varargs: bool,
     pub ret_type: Idx<TypeRef>,
     pub ast_id: FileAstId<ast::Fn>,
 }
 
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub enum Param {
-    Normal(Idx<TypeRef>),
-    Varargs,
-}
-
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct FunctionQualifier {
     pub is_default: bool,
@@ -805,7 +796,6 @@ pub struct Variant {
     pub fields: Fields,
 }
 
-/// A range of densely allocated ItemTree IDs.
 pub struct IdRange<T> {
     range: Range<u32>,
     _p: PhantomData<T>,
@@ -824,12 +814,6 @@ impl<T> Iterator for IdRange<T> {
     }
 }
 
-impl<T> DoubleEndedIterator for IdRange<T> {
-    fn next_back(&mut self) -> Option<Self::Item> {
-        self.range.next_back().map(|raw| Idx::from_raw(raw.into()))
-    }
-}
-
 impl<T> fmt::Debug for IdRange<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_tuple(&format!("IdRange::<{}>", type_name::<T>())).field(&self.range).finish()
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 3f558edd81..7e91b991d2 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -333,8 +333,8 @@ impl Ctx {
         let visibility = self.lower_visibility(func);
         let name = func.name()?.as_name();
 
+        let mut params = Vec::new();
         let mut has_self_param = false;
-        let start_param = self.next_param_idx();
         if let Some(param_list) = func.param_list() {
             if let Some(self_param) = param_list.self_param() {
                 let self_type = match self_param.ty() {
@@ -356,25 +356,22 @@ impl Ctx {
                         }
                     }
                 };
-                let ty = self.data().type_refs.intern(self_type);
-                let idx = self.data().params.alloc(Param::Normal(ty));
-                self.add_attrs(idx.into(), RawAttrs::new(&self_param, &self.hygiene));
+                params.push(self_type);
                 has_self_param = true;
             }
             for param in param_list.params() {
-                let idx = match param.dotdotdot_token() {
-                    Some(_) => self.data().params.alloc(Param::Varargs),
-                    None => {
-                        let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
-                        let ty = self.data().type_refs.intern(type_ref);
-                        self.data().params.alloc(Param::Normal(ty))
-                    }
-                };
-                self.add_attrs(idx.into(), RawAttrs::new(&param, &self.hygiene));
+                let type_ref = TypeRef::from_ast_opt(&self.body_ctx, param.ty());
+                params.push(type_ref);
+            }
+        }
+        let params = params.into_iter().map(|param| self.data().type_refs.intern(param)).collect();
+
+        let mut is_varargs = false;
+        if let Some(params) = func.param_list() {
+            if let Some(last) = params.params().last() {
+                is_varargs = last.dotdotdot_token().is_some();
             }
         }
-        let end_param = self.next_param_idx();
-        let params = IdRange::new(start_param..end_param);
 
         let ret_type = match func.ret_type().and_then(|rt| rt.ty()) {
             Some(type_ref) => TypeRef::from_ast(&self.body_ctx, type_ref),
@@ -422,6 +419,7 @@ impl Ctx {
             qualifier,
             is_in_extern_block: false,
             params,
+            is_varargs,
             ret_type,
             ast_id,
         };
@@ -684,11 +682,9 @@ impl Ctx {
             GenericsOwner::Function(func) => {
                 generics.fill(&self.body_ctx, sm, node);
                 // lower `impl Trait` in arguments
-                for id in func.params.clone() {
-                    if let Param::Normal(ty) = self.data().params[id] {
-                        let ty = self.data().type_refs.lookup(ty);
-                        generics.fill_implicit_impl_trait_args(ty);
-                    }
+                for param in &*func.params {
+                    let param = self.data().type_refs.lookup(*param);
+                    generics.fill_implicit_impl_trait_args(param);
                 }
             }
             GenericsOwner::Struct
@@ -773,11 +769,6 @@ impl Ctx {
             self.tree.data.as_ref().map_or(0, |data| data.variants.len() as u32),
         ))
     }
-    fn next_param_idx(&self) -> Idx<Param> {
-        Idx::from_raw(RawIdx::from(
-            self.tree.data.as_ref().map_or(0, |data| data.params.len() as u32),
-        ))
-    }
 }
 
 fn desugar_future_path(orig: TypeRef) -> Path {
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 3909ad3545..50dc40335a 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -713,38 +713,4 @@ fn main() {
             "#,
         );
     }
-
-    #[test]
-    fn cfgd_out_fn_params() {
-        check_diagnostics(
-            r#"
-fn foo(#[cfg(NEVER)] x: ()) {}
-
-struct S;
-
-impl S {
-    fn method(#[cfg(NEVER)] self) {}
-    fn method2(#[cfg(NEVER)] self, arg: u8) {}
-    fn method3(self, #[cfg(NEVER)] arg: u8) {}
-}
-
-extern "C" {
-    fn fixed(fixed: u8, #[cfg(NEVER)] ...);
-    fn varargs(#[cfg(not(NEVER))] ...);
-}
-
-fn main() {
-    foo();
-    S::method();
-    S::method2(0);
-    S::method3(S);
-    S.method3();
-    unsafe {
-        fixed(0);
-        varargs(1, 2, 3);
-    }
-}
-            "#,
-        )
-    }
 }
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 598b47e417..5072ecea02 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,5 +1,3 @@
-use std::ops::Range;
-
 use either::Either;
 use hir::{HasAttrs, ModuleDef, Semantics};
 use ide_db::{
@@ -7,8 +5,7 @@ use ide_db::{
     RootDatabase,
 };
 use syntax::{
-    ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, TextSize,
-    TokenAtOffset, T,
+    ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T,
 };
 
 use crate::{
@@ -95,18 +92,15 @@ fn extract_positioned_link_from_comment(
     position: FilePosition,
     comment: &ast::Comment,
 ) -> Option<(String, Option<hir::Namespace>)> {
+    let comment_range = comment.syntax().text_range();
     let doc_comment = comment.doc_comment()?;
-    let comment_start =
-        comment.syntax().text_range().start() + TextSize::from(comment.prefix().len() as u32);
     let def_links = extract_definitions_from_markdown(doc_comment);
-    let (def_link, ns, _) = def_links.into_iter().find(|&(_, _, Range { start, end })| {
-        TextRange::at(
-            comment_start + TextSize::from(start as u32),
-            TextSize::from((end - start) as u32),
-        )
-        .contains(position.offset)
+    let start = comment_range.start() + TextSize::from(comment.prefix().len() as u32);
+    let (def_link, ns, _) = def_links.iter().min_by_key(|(_, _, def_link_range)| {
+        let matched_position = start + TextSize::from(def_link_range.start as u32);
+        position.offset.checked_sub(matched_position).unwrap_or_else(|| comment_range.end())
     })?;
-    Some((def_link, ns))
+    Some((def_link.to_string(), *ns))
 }
 
 fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
@@ -1140,7 +1134,7 @@ fn foo<'foo>(_: &'foo ()) {
     fn goto_def_for_intra_doc_link_same_file() {
         check(
             r#"
-/// Blah, [`bar`](bar) .. [`foo`](foo$0) has [`bar`](bar)
+/// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar)
 pub fn bar() { }
 
 /// You might want to see [`std::fs::read()`] too.
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index d57ce40274..7a4f2645fa 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -1,5 +1,7 @@
 //! "Recursive" Syntax highlighting for code in doctests and fixtures.
 
+use std::mem;
+
 use either::Either;
 use hir::{HasAttrs, Semantics};
 use ide_db::call_info::ActiveParameter;
@@ -186,34 +188,44 @@ pub(super) fn doc_comment(hl: &mut Highlights, sema: &Semantics<RootDatabase>, n
             }
         };
 
-        match line.find(RUSTDOC_FENCE) {
-            Some(idx) => {
-                is_codeblock = !is_codeblock;
-                // Check whether code is rust by inspecting fence guards
-                let guards = &line[idx + RUSTDOC_FENCE.len()..];
-                let is_rust =
-                    guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
-                is_doctest = is_codeblock && is_rust;
-                continue;
+        let mut pos = TextSize::from(prefix.len() as u32);
+        let mut range_start = range.start();
+        for line in line.split('\n') {
+            let line_len = TextSize::from(line.len() as u32);
+            let prev_range_start = {
+                let next_range_start = range_start + line_len + TextSize::from(1);
+                mem::replace(&mut range_start, next_range_start)
+            };
+            // only first line has the prefix so take it away for future iterations
+            let mut pos = mem::take(&mut pos);
+
+            match line.find(RUSTDOC_FENCE) {
+                Some(idx) => {
+                    is_codeblock = !is_codeblock;
+                    // Check whether code is rust by inspecting fence guards
+                    let guards = &line[idx + RUSTDOC_FENCE.len()..];
+                    let is_rust =
+                        guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
+                    is_doctest = is_codeblock && is_rust;
+                    continue;
+                }
+                None if !is_doctest => continue,
+                None => (),
             }
-            None if !is_doctest => continue,
-            None => (),
-        }
-
-        let mut pos = TextSize::of(prefix);
-        // whitespace after comment is ignored
-        if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) {
-            pos += TextSize::of(ws);
-        }
-        // lines marked with `#` should be ignored in output, we skip the `#` char
-        if let Some(ws) = line[pos.into()..].chars().next().filter(|&c| c == '#') {
-            pos += TextSize::of(ws);
-        }
 
-        new_comments.push(TextRange::at(range.start(), pos));
+            // whitespace after comment is ignored
+            if let Some(ws) = line[pos.into()..].chars().next().filter(|c| c.is_whitespace()) {
+                pos += TextSize::of(ws);
+            }
+            // lines marked with `#` should be ignored in output, we skip the `#` char
+            if line[pos.into()..].starts_with('#') {
+                pos += TextSize::of('#');
+            }
 
-        inj.add(&line[pos.into()..], TextRange::new(range.start() + pos, range.end()));
-        inj.add_unmapped("\n");
+            new_comments.push(TextRange::at(prev_range_start, pos));
+            inj.add(&line[pos.into()..], TextRange::new(pos, line_len) + prev_range_start);
+            inj.add_unmapped("\n");
+        }
     }
     inj.add_unmapped("\n}");
 
diff --git a/crates/parser/src/grammar/params.rs b/crates/parser/src/grammar/params.rs
index 9e2f02d435..e313f6fb7f 100644
--- a/crates/parser/src/grammar/params.rs
+++ b/crates/parser/src/grammar/params.rs
@@ -41,32 +41,22 @@ fn list_(p: &mut Parser, flavor: Flavor) {
         FnDef | FnTrait | FnPointer => (T!['('], T![')']),
     };
 
-    let list_marker = p.start();
+    let m = p.start();
     p.bump(bra);
 
-    let mut param_marker = None;
     if let FnDef = flavor {
         // test self_param_outer_attr
         // fn f(#[must_use] self) {}
         let m = p.start();
         attributes::outer_attrs(p);
-        match opt_self_param(p, m) {
-            Ok(()) => {}
-            Err(m) => param_marker = Some(m),
-        }
+        opt_self_param(p, m);
     }
 
     while !p.at(EOF) && !p.at(ket) {
         // test param_outer_arg
         // fn f(#[attr1] pat: Type) {}
-        let m = match param_marker.take() {
-            Some(m) => m,
-            None => {
-                let m = p.start();
-                attributes::outer_attrs(p);
-                m
-            }
-        };
+        let m = p.start();
+        attributes::outer_attrs(p);
 
         if !p.at_ts(PARAM_FIRST) {
             p.error("expected value parameter");
@@ -82,12 +72,8 @@ fn list_(p: &mut Parser, flavor: Flavor) {
         }
     }
 
-    if let Some(m) = param_marker {
-        m.abandon(p);
-    }
-
     p.expect(ket);
-    list_marker.complete(p, PARAM_LIST);
+    m.complete(p, PARAM_LIST);
 }
 
 const PARAM_FIRST: TokenSet = patterns::PATTERN_FIRST.union(types::TYPE_FIRST);
@@ -167,7 +153,7 @@ fn variadic_param(p: &mut Parser) -> bool {
 //     fn d(&'a mut self, x: i32) {}
 //     fn e(mut self) {}
 // }
-fn opt_self_param(p: &mut Parser, m: Marker) -> Result<(), Marker> {
+fn opt_self_param(p: &mut Parser, m: Marker) {
     if p.at(T![self]) || p.at(T![mut]) && p.nth(1) == T![self] {
         p.eat(T![mut]);
         self_as_name(p);
@@ -190,7 +176,7 @@ fn opt_self_param(p: &mut Parser, m: Marker) -> Result<(), Marker> {
                 | (T![&], LIFETIME_IDENT, T![self], _)
                 | (T![&], LIFETIME_IDENT, T![mut], T![self])
         ) {
-            return Err(m);
+            return m.abandon(p);
         }
         p.bump(T![&]);
         if p.at(LIFETIME_IDENT) {
@@ -203,7 +189,6 @@ fn opt_self_param(p: &mut Parser, m: Marker) -> Result<(), Marker> {
     if !p.at(T![')']) {
         p.expect(T![,]);
     }
-    Ok(())
 }
 
 fn self_as_name(p: &mut Parser) {


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 45817faf96..d792a23cfc 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -81,7 +81,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="comment documentation">/// </span><span class="comment injected">       comment */</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="variable declaration injected">multi_line_string</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="string_literal injected">"Foo</span>
-    <span class="comment documentation">/// </span><span class="string_literal injected">  bar</span>
+    <span class="comment documentation">/// </span><span class="string_literal injected">  bar</span><span class="escape_sequence injected">\n</span>
     <span class="comment documentation">/// </span><span class="string_literal injected">         "</span><span class="semicolon injected">;</span>
     <span class="comment documentation">///</span>
     <span class="comment documentation">/// ```</span>
@@ -121,4 +121,20 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">cfg_attr</span><span class="parenthesis attribute">(</span><span class="attribute attribute">not</span><span class="parenthesis attribute">(</span><span class="attribute attribute">feature </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"alloc"</span><span class="parenthesis attribute">)</span><span class="comma attribute">,</span><span class="attribute attribute"> doc </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"```ignore"</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</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="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="none injected">alloc::</span><span class="macro injected">vec!</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
 <span class="comment documentation">/// ```</span>
-<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">mix_and_match</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span></code></pre>
\ No newline at end of file
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">mix_and_match</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="comment documentation">/**
+It is beyond me why you'd use these when you got ///
+```rust
+</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="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
+```
+ */</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
+
+<span class="comment documentation">/**
+    Really, I don't get it
+    ```rust
+</span><span class="comment documentation"> </span><span class="none injected">   </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="function injected">example</span><span class="parenthesis injected">(</span><span class="operator injected">&</span><span class="bracket injected">[</span><span class="numeric_literal injected">1</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">2</span><span class="comma injected">,</span><span class="none injected"> </span><span class="numeric_literal injected">3</span><span class="bracket injected">]</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span><span class="comment documentation">
+    ```
+*/</span>
+<span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">block_comments2</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span></code></pre>
\ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index a5ef2d29be..cf0b86ad00 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -516,7 +516,7 @@ impl Foo {
     ///        comment */
     ///
     /// let multi_line_string = "Foo
-    ///   bar
+    ///   bar\n
     ///          ";
     ///
     /// ```
@@ -557,6 +557,22 @@ macro_rules! noop {
 /// let _ = example(&alloc::vec![1, 2, 3]);
 /// ```
 pub fn mix_and_match() {}
+
+/**
+It is beyond me why you'd use these when you got ///
+```rust
+let _ = example(&[1, 2, 3]);
+```
+ */
+pub fn block_comments() {}
+
+/**
+    Really, I don't get it
+    ```rust
+    let _ = example(&[1, 2, 3]);
+    ```
+*/
+pub fn block_comments2() {}
 "#
         .trim(),
         expect_file!["./test_data/highlight_doctest.html"],
diff --git a/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast b/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast
index a84088bf30..495e4c51b5 100644
--- a/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0139_param_outer_arg.rast
@@ -6,16 +6,16 @@ SOURCE_FILE@0..28
       IDENT@3..4 "f"
     PARAM_LIST@4..24
       L_PAREN@4..5 "("
-      PARAM@5..23
-        ATTR@5..13
-          POUND@5..6 "#"
-          L_BRACK@6..7 "["
-          PATH@7..12
-            PATH_SEGMENT@7..12
-              NAME_REF@7..12
-                IDENT@7..12 "attr1"
-          R_BRACK@12..13 "]"
-        WHITESPACE@13..14 " "
+      ATTR@5..13
+        POUND@5..6 "#"
+        L_BRACK@6..7 "["
+        PATH@7..12
+          PATH_SEGMENT@7..12
+            NAME_REF@7..12
+              IDENT@7..12 "attr1"
+        R_BRACK@12..13 "]"
+      WHITESPACE@13..14 " "
+      PARAM@14..23
         IDENT_PAT@14..17
           NAME@14..17
             IDENT@14..17 "pat"
diff --git a/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast b/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast
index 88470c41c0..e10521d853 100644
--- a/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast
+++ b/crates/syntax/test_data/parser/ok/0051_parameter_attrs.rast
@@ -6,25 +6,25 @@ SOURCE_FILE@0..519
       IDENT@3..5 "g1"
     PARAM_LIST@5..34
       L_PAREN@5..6 "("
-      PARAM@6..33
-        ATTR@6..14
-          POUND@6..7 "#"
-          L_BRACK@7..8 "["
-          PATH@8..13
-            PATH_SEGMENT@8..13
-              NAME_REF@8..13
-                IDENT@8..13 "attr1"
-          R_BRACK@13..14 "]"
-        WHITESPACE@14..15 " "
-        ATTR@15..23
-          POUND@15..16 "#"
-          L_BRACK@16..17 "["
-          PATH@17..22
-            PATH_SEGMENT@17..22
-              NAME_REF@17..22
-                IDENT@17..22 "attr2"
-          R_BRACK@22..23 "]"
-        WHITESPACE@23..24 " "
+      ATTR@6..14
+        POUND@6..7 "#"
+        L_BRACK@7..8 "["
+        PATH@8..13
+          PATH_SEGMENT@8..13
+            NAME_REF@8..13
+              IDENT@8..13 "attr1"
+        R_BRACK@13..14 "]"
+      WHITESPACE@14..15 " "
+      ATTR@15..23
+        POUND@15..16 "#"
+        L_BRACK@16..17 "["
+        PATH@17..22
+          PATH_SEGMENT@17..22
+            NAME_REF@17..22
+              IDENT@17..22 "attr2"
+        R_BRACK@22..23 "]"
+      WHITESPACE@23..24 " "
+      PARAM@24..33
         IDENT_PAT@24..27
           NAME@24..27
             IDENT@24..27 "pat"
@@ -48,16 +48,16 @@ SOURCE_FILE@0..519
       IDENT@41..43 "g2"
     PARAM_LIST@43..59
       L_PAREN@43..44 "("
-      PARAM@44..58
-        ATTR@44..52
-          POUND@44..45 "#"
-          L_BRACK@45..46 "["
-          PATH@46..51
-            PATH_SEGMENT@46..51
-              NAME_REF@46..51
-                IDENT@46..51 "attr1"
-          R_BRACK@51..52 "]"
-        WHITESPACE@52..53 " "
+      ATTR@44..52
+        POUND@44..45 "#"
+        L_BRACK@45..46 "["
+        PATH@46..51
+          PATH_SEGMENT@46..51
+            NAME_REF@46..51
+              IDENT@46..51 "attr1"
+        R_BRACK@51..52 "]"
+      WHITESPACE@52..53 " "
+      PARAM@53..58
         IDENT_PAT@53..54
           NAME@53..54
             IDENT@53..54 "x"
@@ -203,16 +203,16 @@ SOURCE_FILE@0..519
           IDENT@193..196 "bar"
         PARAM_LIST@196..233
           L_PAREN@196..197 "("
-          PARAM@197..211
-            ATTR@197..204
-              POUND@197..198 "#"
-              L_BRACK@198..199 "["
-              PATH@199..203
-                PATH_SEGMENT@199..203
-                  NAME_REF@199..203
-                    IDENT@199..203 "attr"
-              R_BRACK@203..204 "]"
-            WHITESPACE@204..205 " "
+          ATTR@197..204
+            POUND@197..198 "#"
+            L_BRACK@198..199 "["
+            PATH@199..203
+              PATH_SEGMENT@199..203
+                NAME_REF@199..203
+                  IDENT@199..203 "attr"
+            R_BRACK@203..204 "]"
+          WHITESPACE@204..205 " "
+          PARAM@205..211
             WILDCARD_PAT@205..206
               UNDERSCORE@205..206 "_"
             COLON@206..207 ":"