Repository: rust-analyzer/rust-analyzer
Pull request #4134 (2020-04-25 09:39:31)

Special case for empty comments in doc comment kind 
Part of #4103

Fix `ui/empty/empty-comment.rs macros`


Patch:
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 2791632576..c3e6532999 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -10,7 +10,7 @@ use ra_syntax::{
 };
 use ra_text_edit::TextEditBuilder;
 
-use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
+use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
 use algo::SyntaxRewriter;
 
 #[derive(Clone, Debug)]
@@ -180,7 +180,6 @@ pub(crate) struct ActionBuilder {
     edit: TextEditBuilder,
     cursor_position: Option<TextUnit>,
     target: Option<TextRange>,
-    file: AssistFile,
 }
 
 impl ActionBuilder {
@@ -242,16 +241,11 @@ impl ActionBuilder {
         algo::diff(&node, &new).into_text_edit(&mut self.edit)
     }
 
-    pub(crate) fn set_file(&mut self, assist_file: AssistFile) {
-        self.file = assist_file
-    }
-
     fn build(self) -> AssistAction {
         AssistAction {
             edit: self.edit.finish(),
             cursor_position: self.cursor_position,
             target: self.target,
-            file: self.file,
         }
     }
 }
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index f185cffdb7..ad4ab66ede 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -3,8 +3,8 @@ use ra_syntax::{
     SyntaxKind, SyntaxNode, TextUnit,
 };
 
-use crate::{Assist, AssistCtx, AssistFile, AssistId};
-use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner};
+use crate::{Assist, AssistCtx, AssistId};
+use ast::{edit::IndentLevel, ArgListOwner, CallExpr, Expr};
 use hir::HirDisplay;
 use rustc_hash::{FxHashMap, FxHashSet};
 
@@ -16,7 +16,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
 // struct Baz;
 // fn baz() -> Baz { Baz }
 // fn foo() {
-//     bar<|>("", baz());
+//      bar<|>("", baz());
 // }
 //
 // ```
@@ -25,7 +25,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
 // struct Baz;
 // fn baz() -> Baz { Baz }
 // fn foo() {
-//     bar("", baz());
+//      bar("", baz());
 // }
 //
 // fn bar(arg: &str, baz: Baz) {
@@ -38,30 +38,21 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
     let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
     let path = path_expr.path()?;
 
+    if path.qualifier().is_some() {
+        return None;
+    }
+
     if ctx.sema.resolve_path(&path).is_some() {
         // The function call already resolves, no need to add a function
         return None;
     }
 
-    let target_module = if let Some(qualifier) = path.qualifier() {
-        if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) =
-            ctx.sema.resolve_path(&qualifier)
-        {
-            Some(module.definition_source(ctx.sema.db))
-        } else {
-            return None;
-        }
-    } else {
-        None
-    };
-
-    let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
+    let function_builder = FunctionBuilder::from_call(&ctx, &call)?;
 
     ctx.add_assist(AssistId("add_function"), "Add function", |edit| {
         edit.target(call.syntax().text_range());
 
         if let Some(function_template) = function_builder.render() {
-            edit.set_file(function_template.file);
             edit.set_cursor(function_template.cursor_offset);
             edit.insert(function_template.insert_offset, function_template.fn_def.to_string());
         }
@@ -72,67 +63,29 @@ struct FunctionTemplate {
     insert_offset: TextUnit,
     cursor_offset: TextUnit,
     fn_def: ast::SourceFile,
-    file: AssistFile,
 }
 
 struct FunctionBuilder {
-    target: GeneratedFunctionTarget,
+    append_fn_at: SyntaxNode,
     fn_name: ast::Name,
     type_params: Option<ast::TypeParamList>,
     params: ast::ParamList,
-    file: AssistFile,
-    needs_pub: bool,
 }
 
 impl FunctionBuilder {
-    /// Prepares a generated function that matches `call` in `generate_in`
-    /// (or as close to `call` as possible, if `generate_in` is `None`)
-    fn from_call(
-        ctx: &AssistCtx,
-        call: &ast::CallExpr,
-        path: &ast::Path,
-        target_module: Option<hir::InFile<hir::ModuleSource>>,
-    ) -> Option<Self> {
-        let needs_pub = target_module.is_some();
-        let mut file = AssistFile::default();
-        let target = if let Some(target_module) = target_module {
-            let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?;
-            file = in_file;
-            target
-        } else {
-            next_space_for_fn_after_call_site(&call)?
-        };
-        let fn_name = fn_name(&path)?;
+    fn from_call(ctx: &AssistCtx, call: &ast::CallExpr) -> Option<Self> {
+        let append_fn_at = next_space_for_fn(&call)?;
+        let fn_name = fn_name(&call)?;
         let (type_params, params) = fn_args(ctx, &call)?;
-        Some(Self { target, fn_name, type_params, params, file, needs_pub })
+        Some(Self { append_fn_at, fn_name, type_params, params })
     }
-
     fn render(self) -> Option<FunctionTemplate> {
         let placeholder_expr = ast::make::expr_todo();
         let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr));
-        let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
-        if self.needs_pub {
-            fn_def = ast::make::add_pub_crate_modifier(fn_def);
-        }
-
-        let (fn_def, insert_offset) = match self.target {
-            GeneratedFunctionTarget::BehindItem(it) => {
-                let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def);
-                let indented = IndentLevel::from_node(&it).increase_indent(with_leading_blank_line);
-                (indented, it.text_range().end())
-            }
-            GeneratedFunctionTarget::InEmptyItemList(it) => {
-                let indent_once = IndentLevel(1);
-                let indent = IndentLevel::from_node(it.syntax());
-
-                let fn_def = ast::make::add_leading_newlines(1, fn_def);
-                let fn_def = indent_once.increase_indent(fn_def);
-                let fn_def = ast::make::add_trailing_newlines(1, fn_def);
-                let fn_def = indent.increase_indent(fn_def);
-                (fn_def, it.syntax().text_range().start() + TextUnit::from_usize(1))
-            }
-        };
-
+        let fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body);
+        let fn_def = ast::make::add_newlines(2, fn_def);
+        let fn_def = IndentLevel::from_node(&self.append_fn_at).increase_indent(fn_def);
+        let insert_offset = self.append_fn_at.text_range().end();
         let cursor_offset_from_fn_start = fn_def
             .syntax()
             .descendants()
@@ -141,24 +94,19 @@ impl FunctionBuilder {
             .text_range()
             .start();
         let cursor_offset = insert_offset + cursor_offset_from_fn_start;
-        Some(FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file })
+        Some(FunctionTemplate { insert_offset, cursor_offset, fn_def })
     }
 }
 
-enum GeneratedFunctionTarget {
-    BehindItem(SyntaxNode),
-    InEmptyItemList(ast::ItemList),
-}
-
-fn fn_name(call: &ast::Path) -> Option<ast::Name> {
-    let name = call.segment()?.syntax().to_string();
+fn fn_name(call: &CallExpr) -> Option<ast::Name> {
+    let name = call.expr()?.syntax().to_string();
     Some(ast::make::name(&name))
 }
 
 /// Computes the type variables and arguments required for the generated function
 fn fn_args(
     ctx: &AssistCtx,
-    call: &ast::CallExpr,
+    call: &CallExpr,
 ) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
     let mut arg_names = Vec::new();
     let mut arg_types = Vec::new();
@@ -210,9 +158,9 @@ fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
     }
 }
 
-fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
+fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
     match fn_arg {
-        ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
+        Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
         _ => Some(
             fn_arg
                 .syntax()
@@ -224,7 +172,7 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
     }
 }
 
-fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
+fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
     let ty = ctx.sema.type_of_expr(fn_arg)?;
     if ty.is_unknown() {
         return None;
@@ -236,7 +184,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
 /// directly after the current block
 /// We want to write the generated function directly after
 /// fns, impls or macro calls, but inside mods
-fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
+fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
     let mut ancestors = expr.syntax().ancestors().peekable();
     let mut last_ancestor: Option<SyntaxNode> = None;
     while let Some(next_ancestor) = ancestors.next() {
@@ -253,32 +201,7 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFu
         }
         last_ancestor = Some(next_ancestor);
     }
-    last_ancestor.map(GeneratedFunctionTarget::BehindItem)
-}
-
-fn next_space_for_fn_in_module(
-    db: &dyn hir::db::AstDatabase,
-    module: hir::InFile<hir::ModuleSource>,
-) -> Option<(AssistFile, GeneratedFunctionTarget)> {
-    let file = module.file_id.original_file(db);
-    let assist_file = AssistFile::TargetFile(file);
-    let assist_item = match module.value {
-        hir::ModuleSource::SourceFile(it) => {
-            if let Some(last_item) = it.items().last() {
-                GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
-            } else {
-                GeneratedFunctionTarget::BehindItem(it.syntax().clone())
-            }
-        }
-        hir::ModuleSource::Module(it) => {
-            if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
-                GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
-            } else {
-                GeneratedFunctionTarget::InEmptyItemList(it.item_list()?)
-            }
-        }
-    };
-    Some((assist_file, assist_item))
+    last_ancestor
 }
 
 #[cfg(test)]
@@ -790,111 +713,6 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
         )
     }
 
-    #[test]
-    fn add_function_in_module() {
-        check_assist(
-            add_function,
-            r"
-mod bar {}
-
-fn foo() {
-    bar::my_fn<|>()
-}
-",
-            r"
-mod bar {
-    pub(crate) fn my_fn() {
-        <|>todo!()
-    }
-}
-
-fn foo() {
-    bar::my_fn()
-}
-",
-        )
-    }
-
-    #[test]
-    fn add_function_in_module_containing_other_items() {
-        check_assist(
-            add_function,
-            r"
-mod bar {
-    fn something_else() {}
-}
-
-fn foo() {
-    bar::my_fn<|>()
-}
-",
-            r"
-mod bar {
-    fn something_else() {}
-
-    pub(crate) fn my_fn() {
-        <|>todo!()
-    }
-}
-
-fn foo() {
-    bar::my_fn()
-}
-",
-        )
-    }
-
-    #[test]
-    fn add_function_in_nested_module() {
-        check_assist(
-            add_function,
-            r"
-mod bar {
-    mod baz {}
-}
-
-fn foo() {
-    bar::baz::my_fn<|>()
-}
-",
-            r"
-mod bar {
-    mod baz {
-        pub(crate) fn my_fn() {
-            <|>todo!()
-        }
-    }
-}
-
-fn foo() {
-    bar::baz::my_fn()
-}
-",
-        )
-    }
-
-    #[test]
-    fn add_function_in_another_file() {
-        check_assist(
-            add_function,
-            r"
-//- /main.rs
-mod foo;
-
-fn main() {
-    foo::bar<|>()
-}
-//- /foo.rs
-",
-            r"
-
-
-pub(crate) fn bar() {
-    <|>todo!()
-}",
-        )
-    }
-
     #[test]
     fn add_function_not_applicable_if_function_already_exists() {
         check_assist_not_applicable(
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index ccc95735f7..a00136da1c 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -17,7 +17,7 @@ mod doc_tests;
 pub mod utils;
 pub mod ast_transform;
 
-use ra_db::{FileId, FileRange};
+use ra_db::FileRange;
 use ra_ide_db::RootDatabase;
 use ra_syntax::{TextRange, TextUnit};
 use ra_text_edit::TextEdit;
@@ -54,7 +54,6 @@ pub struct AssistAction {
     pub cursor_position: Option<TextUnit>,
     // FIXME: This belongs to `AssistLabel`
     pub target: Option<TextRange>,
-    pub file: AssistFile,
 }
 
 #[derive(Debug, Clone)]
@@ -64,18 +63,6 @@ pub struct ResolvedAssist {
     pub action: AssistAction,
 }
 
-#[derive(Debug, Clone, Copy)]
-pub enum AssistFile {
-    CurrentFile,
-    TargetFile(FileId),
-}
-
-impl Default for AssistFile {
-    fn default() -> Self {
-        Self::CurrentFile
-    }
-}
-
 /// Return all the assists applicable at the given position.
 ///
 /// Assists are returned in the "unresolved" state, that is only labels are
@@ -197,7 +184,7 @@ mod helpers {
     use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
     use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
 
-    use crate::{AssistCtx, AssistFile, AssistHandler};
+    use crate::{AssistCtx, AssistHandler};
     use hir::Semantics;
 
     pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
@@ -259,13 +246,7 @@ mod helpers {
             (Some(assist), ExpectedResult::After(after)) => {
                 let action = assist.0[0].action.clone().unwrap();
 
-                let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file {
-                    db.file_text(file_id).as_ref().to_owned()
-                } else {
-                    text_without_caret
-                };
-
-                let mut actual = action.edit.apply(&assisted_file_text);
+                let mut actual = action.edit.apply(&text_without_caret);
                 match action.cursor_position {
                     None => {
                         if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs
index 2b5d11681a..40d56a4f7b 100644
--- a/crates/ra_ide/src/assists.rs
+++ b/crates/ra_ide/src/assists.rs
@@ -37,10 +37,6 @@ fn action_to_edit(
     file_id: FileId,
     assist_label: &AssistLabel,
 ) -> SourceChange {
-    let file_id = match action.file {
-        ra_assists::AssistFile::TargetFile(it) => it,
-        _ => file_id,
-    };
     let file_edit = SourceFileEdit { file_id, edit: action.edit };
     SourceChange::source_file_edit(assist_label.label.clone(), file_edit)
         .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index 4ca0fdf4fb..f0e02180b5 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -5,7 +5,6 @@ mod completion_item;
 mod completion_context;
 mod presentation;
 
-mod complete_attribute;
 mod complete_dot;
 mod complete_record;
 mod complete_pattern;
@@ -79,7 +78,6 @@ pub(crate) fn completions(
     complete_postfix::complete_postfix(&mut acc, &ctx);
     complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
     complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
-    complete_attribute::complete_attribute(&mut acc, &ctx);
 
     Some(acc)
 }
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs
deleted file mode 100644
index b405042e88..0000000000
--- a/crates/ra_ide/src/completion/complete_attribute.rs
+++ /dev/null
@@ -1,587 +0,0 @@
-//! Completion for attributes
-//!
-//! This module uses a bit of static metadata to provide completions
-//! for built-in attributes.
-
-use super::completion_context::CompletionContext;
-use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
-use ra_syntax::{
-    ast::{Attr, AttrKind},
-    AstNode,
-};
-
-pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
-    if !ctx.is_attribute {
-        return;
-    }
-
-    let is_inner = ctx
-        .original_token
-        .ancestors()
-        .find_map(Attr::cast)
-        .map(|attr| attr.kind() == AttrKind::Inner)
-        .unwrap_or(false);
-
-    for attr_completion in ATTRIBUTES {
-        let mut item = CompletionItem::new(
-            CompletionKind::Attribute,
-            ctx.source_range(),
-            attr_completion.label,
-        )
-        .kind(CompletionItemKind::Attribute);
-
-        match (attr_completion.snippet, ctx.config.snippet_cap) {
-            (Some(snippet), Some(cap)) => {
-                item = item.insert_snippet(cap, snippet);
-            }
-            _ => {}
-        }
-
-        if is_inner || !attr_completion.should_be_inner {
-            acc.add(item);
-        }
-    }
-}
-
-struct AttrCompletion {
-    label: &'static str,
-    snippet: Option<&'static str>,
-    should_be_inner: bool,
-}
-
-const ATTRIBUTES: &[AttrCompletion] = &[
-    AttrCompletion { label: "allow", snippet: Some("allow(${0:lint})"), should_be_inner: false },
-    AttrCompletion {
-        label: "cfg_attr",
-        snippet: Some("cfg_attr(${1:predicate}, ${0:attr})"),
-        should_be_inner: false,
-    },
-    AttrCompletion { label: "cfg", snippet: Some("cfg(${0:predicate})"), should_be_inner: false },
-    AttrCompletion { label: "deny", snippet: Some("deny(${0:lint})"), should_be_inner: false },
-    AttrCompletion {
-        label: "deprecated",
-        snippet: Some(r#"deprecated = "${0:reason}""#),
-        should_be_inner: false,
-    },
-    AttrCompletion {
-        label: "derive",
-        snippet: Some(r#"derive(${0:Debug})"#),
-        should_be_inner: false,
-    },
-    AttrCompletion { label: "doc", snippet: Some(r#"doc = "${0:docs}""#), should_be_inner: false },
-    AttrCompletion { label: "feature", snippet: Some("feature(${0:flag})"), should_be_inner: true },
-    AttrCompletion { label: "forbid", snippet: Some("forbid(${0:lint})"), should_be_inner: false },
-    // FIXME: resolve through macro resolution?
-    AttrCompletion { label: "global_allocator", snippet: None, should_be_inner: true },
-    AttrCompletion { label: "ignore", snippet: Some("ignore(${0:lint})"), should_be_inner: false },
-    AttrCompletion { label: "inline", snippet: Some("inline(${0:lint})"), should_be_inner: false },
-    AttrCompletion {
-        label: "link_name",
-        snippet: Some(r#"link_name = "${0:symbol_name}""#),
-        should_be_inner: false,
-    },
-    AttrCompletion { label: "link", snippet: None, should_be_inner: false },
-    AttrCompletion { label: "macro_export", snippet: None, should_be_inner: false },
-    AttrCompletion { label: "macro_use", snippet: None, should_be_inner: false },
-    AttrCompletion {
-        label: "must_use",
-        snippet: Some(r#"must_use = "${0:reason}""#),
-        should_be_inner: false,
-    },
-    AttrCompletion { label: "no_mangle", snippet: None, should_be_inner: false },
-    AttrCompletion { label: "no_std", snippet: None, should_be_inner: true },
-    AttrCompletion { label: "non_exhaustive", snippet: None, should_be_inner: false },
-    AttrCompletion { label: "panic_handler", snippet: None, should_be_inner: true },
-    AttrCompletion { label: "path", snippet: Some("path =\"${0:path}\""), should_be_inner: false },
-    AttrCompletion { label: "proc_macro", snippet: None, should_be_inner: false },
-    AttrCompletion { label: "proc_macro_attribute", snippet: None, should_be_inner: false },
-    AttrCompletion {
-        label: "proc_macro_derive",
-        snippet: Some("proc_macro_derive(${0:Trait})"),
-        should_be_inner: false,
-    },
-    AttrCompletion {
-        label: "recursion_limit",
-        snippet: Some("recursion_limit = ${0:128}"),
-        should_be_inner: true,
-    },
-    AttrCompletion { label: "repr", snippet: Some("repr(${0:C})"), should_be_inner: false },
-    AttrCompletion {
-        label: "should_panic",
-        snippet: Some(r#"expected = "${0:reason}""#),
-        should_be_inner: false,
-    },
-    AttrCompletion {
-        label: "target_feature",
-        snippet: Some("target_feature = \"${0:feature}\""),
-        should_be_inner: false,
-    },
-    AttrCompletion { label: "test", snippet: None, should_be_inner: false },
-    AttrCompletion { label: "used", snippet: None, should_be_inner: false },
-    AttrCompletion { label: "warn", snippet: Some("warn(${0:lint})"), should_be_inner: false },
-    AttrCompletion {
-        label: "windows_subsystem",
-        snippet: Some(r#"windows_subsystem = "${0:subsystem}""#),
-        should_be_inner: true,
-    },
-];
-
-#[cfg(test)]
-mod tests {
-    use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
-    use insta::assert_debug_snapshot;
-
-    fn do_attr_completion(code: &str) -> Vec<CompletionItem> {
-        do_completion(code, CompletionKind::Attribute)
-    }
-
-    #[test]
-    fn test_attribute_completion() {
-        assert_debug_snapshot!(
-        do_attr_completion(
-                r"
-                #[<|>]
-                ",
-        ),
-            @r###"
-        [
-            CompletionItem {
-                label: "allow",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "allow(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "cfg",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "cfg(${0:predicate})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "cfg_attr",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "cfg_attr(${1:predicate}, ${0:attr})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "deny",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "deny(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "deprecated",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "deprecated = \"${0:reason}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "derive",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "derive(${0:Debug})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "doc",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "doc = \"${0:docs}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "forbid",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "forbid(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "ignore",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "ignore(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "inline",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "inline(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "link",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "link",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "link_name",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "link_name = \"${0:symbol_name}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "macro_export",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "macro_export",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "macro_use",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "macro_use",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "must_use",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "must_use = \"${0:reason}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "no_mangle",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "no_mangle",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "non_exhaustive",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "non_exhaustive",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "path",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "path =\"${0:path}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "proc_macro",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "proc_macro",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "proc_macro_attribute",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "proc_macro_attribute",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "proc_macro_derive",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "proc_macro_derive(${0:Trait})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "repr",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "repr(${0:C})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "should_panic",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "expected = \"${0:reason}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "target_feature",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "target_feature = \"${0:feature}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "test",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "test",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "used",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "used",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "warn",
-                source_range: [19; 19),
-                delete: [19; 19),
-                insert: "warn(${0:lint})",
-                kind: Attribute,
-            },
-        ]
-        "###
-        );
-    }
-
-    #[test]
-    fn test_inner_attribute_completion() {
-        assert_debug_snapshot!(
-        do_attr_completion(
-                r"
-                #![<|>]
-                ",
-        ),
-            @r###"
-        [
-            CompletionItem {
-                label: "allow",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "allow(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "cfg",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "cfg(${0:predicate})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "cfg_attr",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "cfg_attr(${1:predicate}, ${0:attr})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "deny",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "deny(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "deprecated",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "deprecated = \"${0:reason}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "derive",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "derive(${0:Debug})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "doc",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "doc = \"${0:docs}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "feature",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "feature(${0:flag})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "forbid",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "forbid(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "global_allocator",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "global_allocator",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "ignore",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "ignore(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "inline",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "inline(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "link",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "link",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "link_name",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "link_name = \"${0:symbol_name}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "macro_export",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "macro_export",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "macro_use",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "macro_use",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "must_use",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "must_use = \"${0:reason}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "no_mangle",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "no_mangle",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "no_std",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "no_std",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "non_exhaustive",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "non_exhaustive",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "panic_handler",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "panic_handler",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "path",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "path =\"${0:path}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "proc_macro",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "proc_macro",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "proc_macro_attribute",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "proc_macro_attribute",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "proc_macro_derive",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "proc_macro_derive(${0:Trait})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "recursion_limit",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "recursion_limit = ${0:128}",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "repr",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "repr(${0:C})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "should_panic",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "expected = \"${0:reason}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "target_feature",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "target_feature = \"${0:feature}\"",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "test",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "test",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "used",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "used",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "warn",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "warn(${0:lint})",
-                kind: Attribute,
-            },
-            CompletionItem {
-                label: "windows_subsystem",
-                source_range: [20; 20),
-                delete: [20; 20),
-                insert: "windows_subsystem = \"${0:subsystem}\"",
-                kind: Attribute,
-            },
-        ]
-        "###
-        );
-    }
-}
diff --git a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
index 6000106d01..270e96df0d 100644
--- a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
+++ b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs
@@ -41,7 +41,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "foo!(…)",
+                label: "foo!",
                 source_range: [46; 46),
                 delete: [46; 46),
                 insert: "foo!($0)",
@@ -81,7 +81,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "vec![…]",
+                label: "vec!",
                 source_range: [280; 280),
                 delete: [280; 280),
                 insert: "vec![$0]",
@@ -118,7 +118,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "foo! {…}",
+                label: "foo!",
                 source_range: [163; 163),
                 delete: [163; 163),
                 insert: "foo! {$0}",
diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs
index 218829b100..a8b4ce114d 100644
--- a/crates/ra_ide/src/completion/complete_pattern.rs
+++ b/crates/ra_ide/src/completion/complete_pattern.rs
@@ -125,7 +125,7 @@ mod tests {
                 kind: Enum,
             },
             CompletionItem {
-                label: "m!(…)",
+                label: "m!",
                 source_range: [151; 151),
                 delete: [151; 151),
                 insert: "m!($0)",
diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs
index 5a5139e14c..d98523406a 100644
--- a/crates/ra_ide/src/completion/complete_qualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_qualified_path.rs
@@ -57,7 +57,9 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
                     }
                     match item {
                         hir::AssocItem::Function(func) => {
-                            acc.add_function(ctx, func, None);
+                            if !func.has_self_param(ctx.db) {
+                                acc.add_function(ctx, func, None);
+                            }
                         }
                         hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
                         hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
@@ -84,7 +86,9 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
                 }
                 match item {
                     hir::AssocItem::Function(func) => {
-                        acc.add_function(ctx, func, None);
+                        if !func.has_self_param(ctx.db) {
+                            acc.add_function(ctx, func, None);
+                        }
                     }
                     hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
                     hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
@@ -478,42 +482,6 @@ mod tests {
         );
     }
 
-    #[test]
-    fn completes_struct_associated_method_with_self() {
-        assert_debug_snapshot!(
-            do_reference_completion(
-                "
-                //- /lib.rs
-                /// A Struct
-                struct S;
-
-                impl S {
-                    /// An associated method
-                    fn m(&self) { }
-                }
-
-                fn foo() { let _ = S::<|> }
-                "
-            ),
-            @r###"
-        [
-            CompletionItem {
-                label: "m()",
-                source_range: [105; 105),
-                delete: [105; 105),
-                insert: "m()$0",
-                kind: Method,
-                lookup: "m",
-                detail: "fn m(&self)",
-                documentation: Documentation(
-                    "An associated method",
-                ),
-            },
-        ]
-        "###
-        );
-    }
-
     #[test]
     fn completes_struct_associated_const() {
         assert_debug_snapshot!(
@@ -901,7 +869,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "foo!(…)",
+                label: "foo!",
                 source_range: [179; 179),
                 delete: [179; 179),
                 insert: "foo!($0)",
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index 8e3dcf96e0..ad00154a32 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -38,15 +38,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext) {
     if let Some(ty) = ctx.expected_type_of(&ctx.token.parent()) {
         if let Some(Adt::Enum(enum_data)) = ty.as_adt() {
             let variants = enum_data.variants(ctx.db);
-
-            let module = if let Some(module) = ctx.scope().module() {
-                // Compute path from the completion site if available.
-                module
-            } else {
-                // Otherwise fall back to the enum's definition site.
-                enum_data.module(ctx.db)
-            };
-
+            let module = enum_data.module(ctx.db);
             for variant in variants {
                 if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
                     // Variants with trivial paths are already added by the existing completion logic,
@@ -741,7 +733,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "bar!(…)",
+                label: "bar!",
                 source_range: [252; 252),
                 delete: [252; 252),
                 insert: "bar!($0)",
@@ -749,7 +741,7 @@ mod tests {
                 detail: "macro_rules! bar",
             },
             CompletionItem {
-                label: "baz!(…)",
+                label: "baz!",
                 source_range: [252; 252),
                 delete: [252; 252),
                 insert: "baz!($0)",
@@ -757,7 +749,7 @@ mod tests {
                 detail: "#[macro_export]\nmacro_rules! baz",
             },
             CompletionItem {
-                label: "foo!(…)",
+                label: "foo!",
                 source_range: [252; 252),
                 delete: [252; 252),
                 insert: "foo!($0)",
@@ -810,7 +802,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "foo!(…)",
+                label: "foo!",
                 source_range: [49; 49),
                 delete: [49; 49),
                 insert: "foo!($0)",
@@ -849,7 +841,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "foo!(…)",
+                label: "foo!",
                 source_range: [57; 57),
                 delete: [57; 57),
                 insert: "foo!($0)",
@@ -888,7 +880,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "foo!(…)",
+                label: "foo!",
                 source_range: [50; 50),
                 delete: [50; 50),
                 insert: "foo!($0)",
@@ -961,7 +953,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "m!(…)",
+                label: "m!",
                 source_range: [145; 145),
                 delete: [145; 145),
                 insert: "m!($0)",
@@ -1014,7 +1006,7 @@ mod tests {
             @r###"
         [
             CompletionItem {
-                label: "m!(…)",
+                label: "m!",
                 source_range: [145; 146),
                 delete: [145; 146),
                 insert: "m!($0)",
@@ -1067,7 +1059,7 @@ mod tests {
                     @r###"
         [
             CompletionItem {
-                label: "m!(…)",
+                label: "m!",
                 source_range: [145; 146),
                 delete: [145; 146),
                 insert: "m!($0)",
@@ -1316,47 +1308,4 @@ mod tests {
         "###
         )
     }
-
-    #[test]
-    fn completes_enum_variant_from_module() {
-        assert_debug_snapshot!(
-            do_reference_completion(
-                r"
-                mod m { pub enum E { V } }
-
-                fn f() -> m::E {
-                    V<|>
-                }
-                "
-            ),
-            @r###"
-        [
-            CompletionItem {
-                label: "f()",
-                source_range: [98; 99),
-                delete: [98; 99),
-                insert: "f()$0",
-                kind: Function,
-                lookup: "f",
-                detail: "fn f() -> m::E",
-            },
-            CompletionItem {
-                label: "m",
-                source_range: [98; 99),
-                delete: [98; 99),
-                insert: "m",
-                kind: Module,
-            },
-            CompletionItem {
-                label: "m::E::V",
-                source_range: [98; 99),
-                delete: [98; 99),
-                insert: "m::E::V",
-                kind: EnumVariant,
-                detail: "()",
-            },
-        ]
-        "###
-        )
-    }
 }
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 37880448ad..a76d1ce450 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -57,7 +57,6 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) is_macro_call: bool,
     pub(super) is_path_type: bool,
     pub(super) has_type_args: bool,
-    pub(super) is_attribute: bool,
 }
 
 impl<'a> CompletionContext<'a> {
@@ -114,7 +113,6 @@ impl<'a> CompletionContext<'a> {
             is_path_type: false,
             has_type_args: false,
             dot_receiver_is_ambiguous_float_literal: false,
-            is_attribute: false,
         };
 
         let mut original_file = original_file.syntax().clone();
@@ -308,7 +306,6 @@ impl<'a> CompletionContext<'a> {
                 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
                 .is_some();
             self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
-            self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some();
 
             self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
             self.has_type_args = segment.type_arg_list().is_some();
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 5936fb8f7c..fb06cc1257 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -121,7 +121,6 @@ pub enum CompletionItemKind {
     Method,
     TypeParam,
     Macro,
-    Attribute,
 }
 
 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
@@ -135,7 +134,6 @@ pub(crate) enum CompletionKind {
     Snippet,
     Postfix,
     BuiltinType,
-    Attribute,
 }
 
 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index 6a6ddc7bd8..5e2b8b920a 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -133,6 +133,29 @@ impl Completions {
         completion_item.kind(kind).set_documentation(docs).add_to(self)
     }
 
+    fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str {
+        let mut votes = [0, 0, 0];
+        for (idx, s) in docs.match_indices(&macro_name) {
+            let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
+            // Ensure to match the full word
+            if after.starts_with('!')
+                && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
+            {
+                // It may have spaces before the braces like `foo! {}`
+                match after[1..].chars().find(|&c| !c.is_whitespace()) {
+                    Some('{') => votes[0] += 1,
+                    Some('[') => votes[1] += 1,
+                    Some('(') => votes[2] += 1,
+                    _ => {}
+                }
+            }
+        }
+
+        // Insert a space before `{}`.
+        // We prefer the last one when some votes equal.
+        *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1
+    }
+
     pub(crate) fn add_macro(
         &mut self,
         ctx: &CompletionContext,
@@ -154,27 +177,21 @@ impl Completions {
         let detail = macro_label(&ast_node);
 
         let docs = macro_.docs(ctx.db);
+        let macro_declaration = format!("{}!", name);
 
-        let mut builder = CompletionItem::new(
-            CompletionKind::Reference,
-            ctx.source_range(),
-            &format!("{}!", name),
-        )
-        .kind(CompletionItemKind::Macro)
-        .set_documentation(docs.clone())
-        .set_deprecated(is_deprecated(macro_, ctx.db))
-        .detail(detail);
+        let mut builder =
+            CompletionItem::new(CompletionKind::Reference, ctx.source_range(), &macro_declaration)
+                .kind(CompletionItemKind::Macro)
+                .set_documentation(docs.clone())
+                .set_deprecated(is_deprecated(macro_, ctx.db))
+                .detail(detail);
 
-        let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call;
         builder = match ctx.config.snippet_cap {
-            Some(cap) if needs_bang => {
-                let docs = docs.as_ref().map_or("", |s| s.as_str());
-                let (bra, ket) = guess_macro_braces(&name, docs);
-                builder
-                    .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket))
-                    .label(format!("{}!{}…{}", name, bra, ket))
+            Some(cap) if ctx.use_item_syntax.is_none() && !ctx.is_macro_call => {
+                let macro_braces_to_insert =
+                    self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str()));
+                builder.insert_snippet(cap, macro_declaration + macro_braces_to_insert)
             }
-            None if needs_bang => builder.insert_text(format!("{}!", name)),
             _ => {
                 tested_by!(dont_insert_macro_call_parens_unncessary);
                 builder.insert_text(name)
@@ -387,34 +404,6 @@ fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
     node.attrs(db).by_key("deprecated").exists()
 }
 
-fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
-    let mut votes = [0, 0, 0];
-    for (idx, s) in docs.match_indices(&macro_name) {
-        let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
-        // Ensure to match the full word
-        if after.starts_with('!')
-            && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
-        {
-            // It may have spaces before the braces like `foo! {}`
-            match after[1..].chars().find(|&c| !c.is_whitespace()) {
-                Some('{') => votes[0] += 1,
-                Some('[') => votes[1] += 1,
-                Some('(') => votes[2] += 1,
-                _ => {}
-            }
-        }
-    }
-
-    // Insert a space before `{}`.
-    // We prefer the last one when some votes equal.
-    let (_vote, (bra, ket)) = votes
-        .iter()
-        .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
-        .max_by_key(|&(&vote, _)| vote)
-        .unwrap();
-    (*bra, *ket)
-}
-
 #[cfg(test)]
 mod tests {
     use insta::assert_debug_snapshot;
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
deleted file mode 100644
index 433f2e0c5e..0000000000
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ /dev/null
@@ -1,82 +0,0 @@
-
-<style>
-body                { margin: 0; }
-pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
-
-.lifetime           { color: #DFAF8F; font-style: italic; }
-.comment            { color: #7F9F7F; }
-.struct, .enum      { color: #7CB8BB; }
-.enum_variant       { color: #BDE0F3; }
-.string_literal     { color: #CC9393; }
-.field              { color: #94BFF3; }
-.function           { color: #93E0E3; }
-.parameter          { color: #94BFF3; }
-.text               { color: #DCDCCC; }
-.type               { color: #7CB8BB; }
-.builtin_type       { color: #8CD0D3; }
-.type_param         { color: #DFAF8F; }
-.attribute          { color: #94BFF3; }
-.numeric_literal    { color: #BFEBBF; }
-.macro              { color: #94BFF3; }
-.module             { color: #AFD8AF; }
-.variable           { color: #DCDCCC; }
-.mutable            { text-decoration: underline; }
-
-.keyword            { color: #F0DFAF; font-weight: bold; }
-.keyword.unsafe     { color: #BC8383; font-weight: bold; }
-.control            { font-style: italic; }
-</style>
-<pre><code><span class="macro">macro_rules!</span> println {
-    ($($arg:tt)*) =&gt; ({
-        $<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*));
-    })
-}
-#[rustc_builtin_macro]
-<span class="macro">macro_rules!</span> format_args_nl {
-    ($fmt:expr) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
-    ($fmt:expr, $($args:tt)*) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
-}
-
-<span class="keyword">fn</span> <span class="function declaration">main</span>() {
-    <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
-    <span class="macro">println!</span>(<span class="string_literal">"Hello"</span>);                 <span class="comment">// =&gt; "Hello"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);   <span class="comment">// =&gt; "Hello, world!"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"The number is </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>);   <span class="comment">// =&gt; "The number is 1"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">?</span><span class="attribute">}</span><span class="string_literal">"</span>, (<span class="numeric_literal">3</span>, <span class="numeric_literal">4</span>));          <span class="comment">// =&gt; "(3, 4)"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">value</span><span class="attribute">}</span><span class="string_literal">"</span>, value=<span class="numeric_literal">4</span>);      <span class="comment">// =&gt; "4"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>);           <span class="comment">// =&gt; "1 2"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">42</span>);             <span class="comment">// =&gt; "0042" with leading zerosV</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>);   <span class="comment">// =&gt; "2 1 1 2"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">argument</span><span class="attribute">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>);   <span class="comment">// =&gt; "test"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>);          <span class="comment">// =&gt; "2 1"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">a</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">c</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">b</span><span class="attribute">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>);  <span class="comment">// =&gt; "a 3 b"</span>
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="variable">width</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, width = <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">-</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">^</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">+</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>,    <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>,   <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="variable">number</span><span class="attribute">:</span><span class="attribute">.</span><span class="variable">prec</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, prec = <span class="numeric_literal">5</span>, number = <span class="numeric_literal">0.01</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 fractional digits"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="numeric_literal">1234.56</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">8</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 right-aligned characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>);
-
-    <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
-
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">\x41</span><span class="attribute">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
-    <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">ничоси</span><span class="attribute">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
-}</code></pre>
\ No newline at end of file
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index c0728bfb12..d9912155b9 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -12,7 +12,7 @@ use ra_ide_db::{
 };
 use ra_prof::profile;
 use ra_syntax::{
-    ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue},
+    ast::{self, HasQuotes, HasStringValue},
     AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
     SyntaxKind::*,
     SyntaxToken, TextRange, WalkEvent, T,
@@ -21,7 +21,6 @@ use rustc_hash::FxHashMap;
 
 use crate::{call_info::ActiveParameter, Analysis, FileId};
 
-use ast::FormatSpecifier;
 pub(crate) use html::highlight_as_html;
 pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
 
@@ -32,81 +31,6 @@ pub struct HighlightedRange {
     pub binding_hash: Option<u64>,
 }
 
-#[derive(Debug)]
-struct HighlightedRangeStack {
-    stack: Vec<Vec<HighlightedRange>>,
-}
-
-/// We use a stack to implement the flattening logic for the highlighted
-/// syntax ranges.
-impl HighlightedRangeStack {
-    fn new() -> Self {
-        Self { stack: vec![Vec::new()] }
-    }
-
-    fn push(&mut self) {
-        self.stack.push(Vec::new());
-    }
-
-    /// Flattens the highlighted ranges.
-    ///
-    /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
-    /// 1) parent-range: Attribute [0, 23)
-    /// 2) child-range: String [16, 21)
-    ///
-    /// The following code implements the flattening, for our example this results to:
-    /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
-    fn pop(&mut self) {
-        let children = self.stack.pop().unwrap();
-        let prev = self.stack.last_mut().unwrap();
-        let needs_flattening = !children.is_empty()
-            && !prev.is_empty()
-            && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
-        if !needs_flattening {
-            prev.extend(children);
-        } else {
-            let mut parent = prev.pop().unwrap();
-            for ele in children {
-                assert!(ele.range.is_subrange(&parent.range));
-                let mut cloned = parent.clone();
-                parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
-                cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
-                if !parent.range.is_empty() {
-                    prev.push(parent);
-                }
-                prev.push(ele);
-                parent = cloned;
-            }
-            if !parent.range.is_empty() {
-                prev.push(parent);
-            }
-        }
-    }
-
-    fn add(&mut self, range: HighlightedRange) {
-        self.stack
-            .last_mut()
-            .expect("during DFS traversal, the stack must not be empty")
-            .push(range)
-    }
-
-    fn flattened(mut self) -> Vec<HighlightedRange> {
-        assert_eq!(
-            self.stack.len(),
-            1,
-            "after DFS traversal, the stack should only contain a single element"
-        );
-        let mut res = self.stack.pop().unwrap();
-        res.sort_by_key(|range| range.range.start());
-        // Check that ranges are sorted and disjoint
-        assert!(res
-            .iter()
-            .zip(res.iter().skip(1))
-            .all(|(left, right)| left.range.end() <= right.range.start()));
-        res
-    }
-}
-
 pub(crate) fn highlight(
     db: &RootDatabase,
     file_id: FileId,
@@ -133,18 +57,52 @@ pub(crate) fn highlight(
     let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
     // We use a stack for the DFS traversal below.
     // When we leave a node, the we use it to flatten the highlighted ranges.
-    let mut stack = HighlightedRangeStack::new();
+    let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()];
 
     let mut current_macro_call: Option<ast::MacroCall> = None;
-    let mut format_string: Option<SyntaxElement> = None;
 
     // Walk all nodes, keeping track of whether we are inside a macro or not.
     // If in macro, expand it first and highlight the expanded code.
     for event in root.preorder_with_tokens() {
         match &event {
-            WalkEvent::Enter(_) => stack.push(),
-            WalkEvent::Leave(_) => stack.pop(),
+            WalkEvent::Enter(_) => res.push(Vec::new()),
+            WalkEvent::Leave(_) => {
+                /* Flattens the highlighted ranges.
+                 *
+                 * For example `#[cfg(feature = "foo")]` contains the nested ranges:
+                 * 1) parent-range: Attribute [0, 23)
+                 * 2) child-range: String [16, 21)
+                 *
+                 * The following code implements the flattening, for our example this results to:
+                 * `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
+                 */
+                let children = res.pop().unwrap();
+                let prev = res.last_mut().unwrap();
+                let needs_flattening = !children.is_empty()
+                    && !prev.is_empty()
+                    && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
+                if !needs_flattening {
+                    prev.extend(children);
+                } else {
+                    let mut parent = prev.pop().unwrap();
+                    for ele in children {
+                        assert!(ele.range.is_subrange(&parent.range));
+                        let mut cloned = parent.clone();
+                        parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
+                        cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
+                        if !parent.range.is_empty() {
+                            prev.push(parent);
+                        }
+                        prev.push(ele);
+                        parent = cloned;
+                    }
+                    if !parent.range.is_empty() {
+                        prev.push(parent);
+                    }
+                }
+            }
         };
+        let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
 
         let event_range = match &event {
             WalkEvent::Enter(it) => it.text_range(),
@@ -161,7 +119,7 @@ pub(crate) fn highlight(
             WalkEvent::Enter(Some(mc)) => {
                 current_macro_call = Some(mc.clone());
                 if let Some(range) = macro_call_range(&mc) {
-                    stack.add(HighlightedRange {
+                    current.push(HighlightedRange {
                         range,
                         highlight: HighlightTag::Macro.into(),
                         binding_hash: None,
@@ -172,7 +130,6 @@ pub(crate) fn highlight(
             WalkEvent::Leave(Some(mc)) => {
                 assert!(current_macro_call == Some(mc));
                 current_macro_call = None;
-                format_string = None;
                 continue;
             }
             _ => (),
@@ -193,30 +150,6 @@ pub(crate) fn highlight(
             };
             let token = sema.descend_into_macros(token.clone());
             let parent = token.parent();
-
-            // Check if macro takes a format string and remember it for highlighting later.
-            // The macros that accept a format string expand to a compiler builtin macros
-            // `format_args` and `format_args_nl`.
-            if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) {
-                if let Some(name) =
-                    fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref())
-                {
-                    match name.text().as_str() {
-                        "format_args" | "format_args_nl" => {
-                            format_string = parent
-                                .children_with_tokens()
-                                .filter(|t| t.kind() != WHITESPACE)
-                                .nth(1)
-                                .filter(|e| {
-                                    ast::String::can_cast(e.kind())
-                                        || ast::RawString::can_cast(e.kind())
-                                })
-                        }
-                        _ => {}
-                    }
-                }
-            }
-
             // We only care Name and Name_ref
             match (token.kind(), parent.kind()) {
                 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -228,72 +161,27 @@ pub(crate) fn highlight(
 
         if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
             let expanded = element_to_highlight.as_token().unwrap().clone();
-            if highlight_injection(&mut stack, &sema, token, expanded).is_some() {
+            if highlight_injection(current, &sema, token, expanded).is_some() {
                 continue;
             }
         }
 
-        let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
-
         if let Some((highlight, binding_hash)) =
-            highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone())
+            highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
         {
-            stack.add(HighlightedRange { range, highlight, binding_hash });
-            if let Some(string) =
-                element_to_highlight.as_token().cloned().and_then(ast::String::cast)
-            {
-                stack.push();
-                if is_format_string {
-                    string.lex_format_specifier(|piece_range, kind| {
-                        if let Some(highlight) = highlight_format_specifier(kind) {
-                            stack.add(HighlightedRange {
-                                range: piece_range + range.start(),
-                                highlight: highlight.into(),
-                                binding_hash: None,
-                            });
-                        }
-                    });
-                }
-                stack.pop();
-            } else if let Some(string) =
-                element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
-            {
-                stack.push();
-                if is_format_string {
-                    string.lex_format_specifier(|piece_range, kind| {
-                        if let Some(highlight) = highlight_format_specifier(kind) {
-                            stack.add(HighlightedRange {
-                                range: piece_range + range.start(),
-                                highlight: highlight.into(),
-                                binding_hash: None,
-                            });
-                        }
-                    });
-                }
-                stack.pop();
-            }
+            current.push(HighlightedRange { range, highlight, binding_hash });
         }
     }
 
-    stack.flattened()
-}
-
-fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
-    Some(match kind {
-        FormatSpecifier::Open
-        | FormatSpecifier::Close
-        | FormatSpecifier::Colon
-        | FormatSpecifier::Fill
-        | FormatSpecifier::Align
-        | FormatSpecifier::Sign
-        | FormatSpecifier::NumberSign
-        | FormatSpecifier::DollarSign
-        | FormatSpecifier::Dot
-        | FormatSpecifier::Asterisk
-        | FormatSpecifier::QuestionMark => HighlightTag::Attribute,
-        FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
-        FormatSpecifier::Identifier => HighlightTag::Local,
-    })
+    assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element");
+    let mut res = res.pop().unwrap();
+    res.sort_by_key(|range| range.range.start());
+    // Check that ranges are sorted and disjoint
+    assert!(res
+        .iter()
+        .zip(res.iter().skip(1))
+        .all(|(left, right)| left.range.end() <= right.range.start()));
+    res
 }
 
 fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -471,7 +359,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
 }
 
 fn highlight_injection(
-    acc: &mut HighlightedRangeStack,
+    acc: &mut Vec<HighlightedRange>,
     sema: &Semantics<RootDatabase>,
     literal: ast::RawString,
     expanded: SyntaxToken,
@@ -484,7 +372,7 @@ fn highlight_injection(
     let (analysis, tmp_file_id) = Analysis::from_single_file(value);
 
     if let Some(range) = literal.open_quote_text_range() {
-        acc.add(HighlightedRange {
+        acc.push(HighlightedRange {
             range,
             highlight: HighlightTag::StringLiteral.into(),
             binding_hash: None,
@@ -494,12 +382,12 @@ fn highlight_injection(
     for mut h in analysis.highlight(tmp_file_id).unwrap() {
         if let Some(r) = literal.map_range_up(h.range) {
             h.range = r;
-            acc.add(h)
+            acc.push(h)
         }
     }
 
     if let Some(range) = literal.close_quote_text_range() {
-        acc.add(HighlightedRange {
+        acc.push(HighlightedRange {
             range,
             highlight: HighlightTag::StringLiteral.into(),
             binding_hash: None,
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 362ee30fe0..84008b2e34 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -141,11 +141,6 @@ impl CargoWorkspace {
         cargo_toml: &Path,
         cargo_features: &CargoConfig,
     ) -> Result<CargoWorkspace> {
-        let _ = Command::new(cargo_binary())
-            .arg("--version")
-            .output()
-            .context("failed to run `cargo --version`, is `cargo` in PATH?")?;
-
         let mut meta = MetadataCommand::new();
         meta.manifest_path(cargo_toml);
         if cargo_features.all_features {
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index ee0f5cc406..0f4a50be47 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -293,20 +293,11 @@ pub fn fn_def(
     ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body))
 }
 
-pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
+pub fn add_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
     let newlines = "\n".repeat(amount_of_newlines);
     ast_from_text(&format!("{}{}", newlines, t.syntax()))
 }
 
-pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile {
-    let newlines = "\n".repeat(amount_of_newlines);
-    ast_from_text(&format!("{}{}", t.syntax(), newlines))
-}
-
-pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef {
-    ast_from_text(&format!("pub(crate) {}", fn_def))
-}
-
 fn ast_from_text<N: AstNode>(text: &str) -> N {
     let parse = SourceFile::parse(text);
     let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs
index aa34b682d9..a60768056d 100644
--- a/crates/ra_syntax/src/ast/tokens.rs
+++ b/crates/ra_syntax/src/ast/tokens.rs
@@ -56,6 +56,9 @@ const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = {
 };
 
 fn kind_by_prefix(text: &str) -> CommentKind {
+    if text == "/**/" {
+        return CommentKind { shape: CommentShape::Block, doc: None };
+    }
     for (prefix, kind) in COMMENT_PREFIX_TO_KIND.iter() {
         if text.starts_with(prefix) {
             return *kind;
@@ -172,362 +175,3 @@ impl RawString {
         Some(range + contents_range.start())
     }
 }
-
-#[derive(Debug)]
-pub enum FormatSpecifier {
-    Open,
-    Close,
-    Integer,
-    Identifier,
-    Colon,
-    Fill,
-    Align,
-    Sign,
-    NumberSign,
-    Zero,
-    DollarSign,
-    Dot,
-    Asterisk,
-    QuestionMark,
-}
-
-pub trait HasFormatSpecifier: AstToken {
-    fn char_ranges(
-        &self,
-    ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>;
-
-    fn lex_format_specifier<F>(&self, mut callback: F)
-    where
-        F: FnMut(TextRange, FormatSpecifier),
-    {
-        let char_ranges = if let Some(char_ranges) = self.char_ranges() {
-            char_ranges
-        } else {
-            return;
-        };
-        let mut chars = char_ranges.iter().peekable();
-
-        while let Some((range, first_char)) = chars.next() {
-            match first_char {
-                Ok('{') => {
-                    // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
-                    if let Some((_, Ok('{'))) = chars.peek() {
-                        // Escaped format specifier, `{{`
-                        chars.next();
-                        continue;
-                    }
-
-                    callback(*range, FormatSpecifier::Open);
-
-                    // check for integer/identifier
-                    match chars
-                        .peek()
-                        .and_then(|next| next.1.as_ref().ok())
-                        .copied()
-                        .unwrap_or_default()
-                    {
-                        '0'..='9' => {
-                            // integer
-                            read_integer(&mut chars, &mut callback);
-                        }
-                        c if c == '_' || c.is_alphabetic() => {
-                            // identifier
-                            read_identifier(&mut chars, &mut callback);
-                        }
-                        _ => {}
-                    }
-
-                    if let Some((_, Ok(':'))) = chars.peek() {
-                        skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback);
-
-                        // check for fill/align
-                        let mut cloned = chars.clone().take(2);
-                        let first = cloned
-                            .next()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default();
-                        let second = cloned
-                            .next()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default();
-                        match second {
-                            '<' | '^' | '>' => {
-                                // alignment specifier, first char specifies fillment
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::Fill,
-                                    &mut callback,
-                                );
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::Align,
-                                    &mut callback,
-                                );
-                            }
-                            _ => match first {
-                                '<' | '^' | '>' => {
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::Align,
-                                        &mut callback,
-                                    );
-                                }
-                                _ => {}
-                            },
-                        }
-
-                        // check for sign
-                        match chars
-                            .peek()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default()
-                        {
-                            '+' | '-' => {
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::Sign,
-                                    &mut callback,
-                                );
-                            }
-                            _ => {}
-                        }
-
-                        // check for `#`
-                        if let Some((_, Ok('#'))) = chars.peek() {
-                            skip_char_and_emit(
-                                &mut chars,
-                                FormatSpecifier::NumberSign,
-                                &mut callback,
-                            );
-                        }
-
-                        // check for `0`
-                        let mut cloned = chars.clone().take(2);
-                        let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-                        let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-
-                        if first == Some('0') && second != Some('$') {
-                            skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback);
-                        }
-
-                        // width
-                        match chars
-                            .peek()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default()
-                        {
-                            '0'..='9' => {
-                                read_integer(&mut chars, &mut callback);
-                                if let Some((_, Ok('$'))) = chars.peek() {
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::DollarSign,
-                                        &mut callback,
-                                    );
-                                }
-                            }
-                            c if c == '_' || c.is_alphabetic() => {
-                                read_identifier(&mut chars, &mut callback);
-                                if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
-                                    != Some('$')
-                                {
-                                    continue;
-                                }
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::DollarSign,
-                                    &mut callback,
-                                );
-                            }
-                            _ => {}
-                        }
-
-                        // precision
-                        if let Some((_, Ok('.'))) = chars.peek() {
-                            skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback);
-
-                            match chars
-                                .peek()
-                                .and_then(|next| next.1.as_ref().ok())
-                                .copied()
-                                .unwrap_or_default()
-                            {
-                                '*' => {
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::Asterisk,
-                                        &mut callback,
-                                    );
-                                }
-                                '0'..='9' => {
-                                    read_integer(&mut chars, &mut callback);
-                                    if let Some((_, Ok('$'))) = chars.peek() {
-                                        skip_char_and_emit(
-                                            &mut chars,
-                                            FormatSpecifier::DollarSign,
-                                            &mut callback,
-                                        );
-                                    }
-                                }
-                                c if c == '_' || c.is_alphabetic() => {
-                                    read_identifier(&mut chars, &mut callback);
-                                    if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
-                                        != Some('$')
-                                    {
-                                        continue;
-                                    }
-                                    skip_char_and_emit(
-                                        &mut chars,
-                                        FormatSpecifier::DollarSign,
-                                        &mut callback,
-                                    );
-                                }
-                                _ => {
-                                    continue;
-                                }
-                            }
-                        }
-
-                        // type
-                        match chars
-                            .peek()
-                            .and_then(|next| next.1.as_ref().ok())
-                            .copied()
-                            .unwrap_or_default()
-                        {
-                            '?' => {
-                                skip_char_and_emit(
-                                    &mut chars,
-                                    FormatSpecifier::QuestionMark,
-                                    &mut callback,
-                                );
-                            }
-                            c if c == '_' || c.is_alphabetic() => {
-                                read_identifier(&mut chars, &mut callback);
-                            }
-                            _ => {}
-                        }
-                    }
-
-                    let mut cloned = chars.clone().take(2);
-                    let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-                    let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
-                    if first != Some('}') {
-                        continue;
-                    }
-                    if second == Some('}') {
-                        // Escaped format end specifier, `}}`
-                        continue;
-                    }
-                    skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
-                }
-                _ => {
-                    while let Some((_, Ok(next_char))) = chars.peek() {
-                        match next_char {
-                            '{' => break,
-                            _ => {}
-                        }
-                        chars.next();
-                    }
-                }
-            };
-        }
-
-        fn skip_char_and_emit<'a, I, F>(
-            chars: &mut std::iter::Peekable<I>,
-            emit: FormatSpecifier,
-            callback: &mut F,
-        ) where
-            I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
-            F: FnMut(TextRange, FormatSpecifier),
-        {
-            let (range, _) = chars.next().unwrap();
-            callback(*range, emit);
-        }
-
-        fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
-        where
-            I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
-            F: FnMut(TextRange, FormatSpecifier),
-        {
-            let (mut range, c) = chars.next().unwrap();
-            assert!(c.as_ref().unwrap().is_ascii_digit());
-            while let Some((r, Ok(next_char))) = chars.peek() {
-                if next_char.is_ascii_digit() {
-                    chars.next();
-                    range = range.extend_to(r);
-                } else {
-                    break;
-                }
-            }
-            callback(range, FormatSpecifier::Integer);
-        }
-
-        fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
-        where
-            I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
-            F: FnMut(TextRange, FormatSpecifier),
-        {
-            let (mut range, c) = chars.next().unwrap();
-            assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_');
-            while let Some((r, Ok(next_char))) = chars.peek() {
-                if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() {
-                    chars.next();
-                    range = range.extend_to(r);
-                } else {
-                    break;
-                }
-            }
-            callback(range, FormatSpecifier::Identifier);
-        }
-    }
-}
-
-impl HasFormatSpecifier for String {
-    fn char_ranges(
-        &self,
-    ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
-        let text = self.text().as_str();
-        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
-        let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
-
-        let mut res = Vec::with_capacity(text.len());
-        rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| {
-            res.push((
-                TextRange::from_to(
-                    TextUnit::from_usize(range.start),
-                    TextUnit::from_usize(range.end),
-                ) + offset,
-                unescaped_char,
-            ))
-        });
-
-        Some(res)
-    }
-}
-
-impl HasFormatSpecifier for RawString {
-    fn char_ranges(
-        &self,
-    ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
-        let text = self.text().as_str();
-        let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
-        let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
-
-        let mut res = Vec::with_capacity(text.len());
-        for (idx, c) in text.char_indices() {
-            res.push((
-                TextRange::from_to(
-                    TextUnit::from_usize(idx),
-                    TextUnit::from_usize(idx + c.len_utf8()),
-                ) + offset,
-                Ok(c),
-            ));
-        }
-        Some(res)
-    }
-}
diff --git a/crates/rust-analyzer/src/conv.rs b/crates/rust-analyzer/src/conv.rs
index 2285cb1d3d..098ee369c2 100644
--- a/crates/rust-analyzer/src/conv.rs
+++ b/crates/rust-analyzer/src/conv.rs
@@ -100,7 +100,6 @@ impl Conv for CompletionItemKind {
             CompletionItemKind::Method => Method,
             CompletionItemKind::TypeParam => TypeParameter,
             CompletionItemKind::Macro => Method,
-            CompletionItemKind::Attribute => EnumMember,
         }
     }
 }
diff --git a/docs/user/assists.md b/docs/user/assists.md
index 6c69436223..6483ba4f3e 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -65,7 +65,7 @@ Adds a stub function with a signature matching the function under the cursor.
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar┃("", baz());
+     bar┃("", baz());
 }
 
 
@@ -73,7 +73,7 @@ fn foo() {
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar("", baz());
+     bar("", baz());
 }
 
 fn bar(arg: &str, baz: Baz) {
diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts
index af8f1df511..399a150c65 100644
--- a/editors/code/src/source_change.ts
+++ b/editors/code/src/source_change.ts
@@ -37,13 +37,11 @@ export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) {
             toReveal.position,
         );
         const editor = vscode.window.activeTextEditor;
-        if (!editor || !editor.selection.isEmpty) {
+        if (!editor || editor.document.uri.toString() !== uri.toString()) {
             return;
         }
-
-        if (editor.document.uri !== uri) {
-            const doc = await vscode.workspace.openTextDocument(uri);
-            await vscode.window.showTextDocument(doc);
+        if (!editor.selection.isEmpty) {
+            return;
         }
         editor.selection = new vscode.Selection(position, position);
         editor.revealRange(


Test patch:
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index e4fa9ee366..b39e60870e 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -66,7 +66,7 @@ fn doctest_add_function() {
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar<|>("", baz());
+     bar<|>("", baz());
 }
 
 "#####,
@@ -74,7 +74,7 @@ fn foo() {
 struct Baz;
 fn baz() -> Baz { Baz }
 fn foo() {
-    bar("", baz());
+     bar("", baz());
 }
 
 fn bar(arg: &str, baz: Baz) {
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index a9aae957f0..73611e23a5 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -168,73 +168,3 @@ macro_rules! test {}
     );
     let _ = analysis.highlight(file_id).unwrap();
 }
-
-#[test]
-fn test_string_highlighting() {
-    // The format string detection is based on macro-expansion,
-    // thus, we have to copy the macro definition from `std`
-    let (analysis, file_id) = single_file(
-        r#"
-macro_rules! println {
-    ($($arg:tt)*) => ({
-        $crate::io::_print($crate::format_args_nl!($($arg)*));
-    })
-}
-#[rustc_builtin_macro]
-macro_rules! format_args_nl {
-    ($fmt:expr) => {{ /* compiler built-in */ }};
-    ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
-}
-
-fn main() {
-    // from https://doc.rust-lang.org/std/fmt/index.html
-    println!("Hello");                 // => "Hello"
-    println!("Hello, {}!", "world");   // => "Hello, world!"
-    println!("The number is {}", 1);   // => "The number is 1"
-    println!("{:?}", (3, 4));          // => "(3, 4)"
-    println!("{value}", value=4);      // => "4"
-    println!("{} {}", 1, 2);           // => "1 2"
-    println!("{:04}", 42);             // => "0042" with leading zerosV
-    println!("{1} {} {0} {}", 1, 2);   // => "2 1 1 2"
-    println!("{argument}", argument = "test");   // => "test"
-    println!("{name} {}", 1, name = 2);          // => "2 1"
-    println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
-    println!("Hello {:5}!", "x");
-    println!("Hello {:1$}!", "x", 5);
-    println!("Hello {1:0$}!", 5, "x");
-    println!("Hello {:width$}!", "x", width = 5);
-    println!("Hello {:<5}!", "x");
-    println!("Hello {:-<5}!", "x");
-    println!("Hello {:^5}!", "x");
-    println!("Hello {:>5}!", "x");
-    println!("Hello {:+}!", 5);
-    println!("{:#x}!", 27);
-    println!("Hello {:05}!", 5);
-    println!("Hello {:05}!", -5);
-    println!("{:#010x}!", 27);
-    println!("Hello {0} is {1:.5}", "x", 0.01);
-    println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
-    println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
-    println!("Hello {} is {:.*}",    "x", 5, 0.01);
-    println!("Hello {} is {2:.*}",   "x", 5, 0.01);
-    println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
-    println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
-    println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
-    println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
-    println!("Hello {{}}");
-    println!("{{ Hello");
-
-    println!(r"Hello, {}!", "world");
-
-    println!("{\x41}", A = 92);
-    println!("{ничоси}", ничоси = 92);
-}"#
-        .trim(),
-    );
-
-    let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html");
-    let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
-    let expected_html = &read_text(&dst_file);
-    fs::write(dst_file, &actual_html).unwrap();
-    assert_eq_text!(expected_html, actual_html);
-}
diff --git a/crates/ra_mbe/src/tests.rs b/crates/ra_mbe/src/tests.rs
index 100ed41f23..76f5715028 100644
--- a/crates/ra_mbe/src/tests.rs
+++ b/crates/ra_mbe/src/tests.rs
@@ -1849,3 +1849,16 @@ fn test_expand_bad_literal() {
     )
     .assert_expand_err(r#"foo!(&k");"#, &ExpandError::BindingError("".into()));
 }
+
+#[test]
+fn test_empty_comments() {
+    parse_macro(
+        r#"
+        macro_rules! one_arg_macro { ($fmt:expr) => (); }
+    "#,
+    )
+    .assert_expand_err(
+        r#"one_arg_macro!(/**/)"#,
+        &ExpandError::BindingError("expected Expr".into()),
+    );
+}