Repository: rust-analyzer/rust-analyzer
Pull request #8641 (2021-04-24 06:09:07)

minor
bors r+
🤖


Patch:
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs
index e5ae718c9f..4a3ed7783e 100644
--- a/crates/ide_assists/src/ast_transform.rs
+++ b/crates/ide_assists/src/ast_transform.rs
@@ -3,27 +3,20 @@ use hir::{HirDisplay, PathResolution, SemanticsScope};
 use ide_db::helpers::mod_path_to_ast;
 use rustc_hash::FxHashMap;
 use syntax::{
+    algo::SyntaxRewriter,
     ast::{self, AstNode},
-    ted, SyntaxNode,
+    SyntaxNode,
 };
 
-pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
-    let mut skip_to = None;
-    for event in node.syntax().preorder() {
-        match event {
-            syntax::WalkEvent::Enter(node) if skip_to.is_none() => {
-                skip_to = transformer.get_substitution(&node, transformer).zip(Some(node));
-            }
-            syntax::WalkEvent::Enter(_) => (),
-            syntax::WalkEvent::Leave(node) => match &skip_to {
-                Some((replacement, skip_target)) if *skip_target == node => {
-                    ted::replace(node, replacement.clone_for_update());
-                    skip_to.take();
-                }
-                _ => (),
-            },
+pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
+    SyntaxRewriter::from_fn(|element| match element {
+        syntax::SyntaxElement::Node(n) => {
+            let replacement = transformer.get_substitution(&n, transformer)?;
+            Some(replacement.into())
         }
-    }
+        _ => None,
+    })
+    .rewrite_ast(&node)
 }
 
 /// `AstTransform` helps with applying bulk transformations to syntax nodes.
@@ -198,9 +191,11 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
                 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
                 let mut path = mod_path_to_ast(&found_path);
 
-                let type_args = p.segment().and_then(|s| s.generic_arg_list());
+                let type_args = p
+                    .segment()
+                    .and_then(|s| s.generic_arg_list())
+                    .map(|arg_list| apply(recur, arg_list));
                 if let Some(type_args) = type_args {
-                    apply(recur, &type_args);
                     let last_segment = path.segment().unwrap();
                     path = path.with_segment(last_segment.with_generic_args(type_args))
                 }
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index a454a2af32..49aa70f74e 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -93,7 +93,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
 
     let range = ctx.sema.original_range(&syntax_under_caret).range;
     let group_label = group_label(import_assets.import_candidate());
-    let scope = ImportScope::find_insert_use_container_with_macros(&syntax_under_caret, &ctx.sema)?;
+    let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?;
     for import in proposed_imports {
         acc.add_group(
             &group_label,
@@ -101,11 +101,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
             format!("Import `{}`", import.import_path),
             range,
             |builder| {
-                let scope = match scope.clone() {
-                    ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)),
-                    ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)),
-                };
-                insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
+                let rewriter =
+                    insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
+                builder.rewrite(rewriter);
             },
         );
     }
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index 66f274fa78..a8d6355bdd 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -5,7 +5,7 @@ use hir::{Module, ModuleDef, Name, Variant};
 use ide_db::{
     defs::Definition,
     helpers::{
-        insert_use::{insert_use, ImportScope, InsertUseConfig},
+        insert_use::{insert_use, ImportScope},
         mod_path_to_ast,
     },
     search::FileReference,
@@ -13,9 +13,9 @@ use ide_db::{
 };
 use rustc_hash::FxHashSet;
 use syntax::{
-    algo::find_node_at_offset,
-    ast::{self, make, AstNode, NameOwner, VisibilityOwner},
-    ted, SyntaxNode, T,
+    algo::{find_node_at_offset, SyntaxRewriter},
+    ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
+    SourceFile, SyntaxElement, SyntaxNode, T,
 };
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -62,50 +62,40 @@ pub(crate) fn extract_struct_from_enum_variant(
             let mut visited_modules_set = FxHashSet::default();
             let current_module = enum_hir.module(ctx.db());
             visited_modules_set.insert(current_module);
-            // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
-            let mut def_file_references = None;
+            let mut def_rewriter = None;
             for (file_id, references) in usages {
+                let mut rewriter = SyntaxRewriter::default();
+                let source_file = ctx.sema.parse(file_id);
+                for reference in references {
+                    update_reference(
+                        ctx,
+                        &mut rewriter,
+                        reference,
+                        &source_file,
+                        &enum_module_def,
+                        &variant_hir_name,
+                        &mut visited_modules_set,
+                    );
+                }
                 if file_id == ctx.frange.file_id {
-                    def_file_references = Some(references);
+                    def_rewriter = Some(rewriter);
                     continue;
                 }
                 builder.edit_file(file_id);
-                let source_file = builder.make_ast_mut(ctx.sema.parse(file_id));
-                let processed = process_references(
-                    ctx,
-                    &mut visited_modules_set,
-                    source_file.syntax(),
-                    &enum_module_def,
-                    &variant_hir_name,
-                    references,
-                );
-                processed.into_iter().for_each(|(path, node, import)| {
-                    apply_references(ctx.config.insert_use, path, node, import)
-                });
+                builder.rewrite(rewriter);
             }
+            let mut rewriter = def_rewriter.unwrap_or_default();
+            update_variant(&mut rewriter, &variant);
+            extract_struct_def(
+                &mut rewriter,
+                &enum_ast,
+                variant_name.clone(),
+                &field_list,
+                &variant.parent_enum().syntax().clone().into(),
+                enum_ast.visibility(),
+            );
             builder.edit_file(ctx.frange.file_id);
-            let source_file = builder.make_ast_mut(ctx.sema.parse(ctx.frange.file_id));
-            let variant = builder.make_ast_mut(variant.clone());
-            if let Some(references) = def_file_references {
-                let processed = process_references(
-                    ctx,
-                    &mut visited_modules_set,
-                    source_file.syntax(),
-                    &enum_module_def,
-                    &variant_hir_name,
-                    references,
-                );
-                processed.into_iter().for_each(|(path, node, import)| {
-                    apply_references(ctx.config.insert_use, path, node, import)
-                });
-            }
-
-            let def = create_struct_def(variant_name.clone(), &field_list, enum_ast.visibility());
-            let start_offset = &variant.parent_enum().syntax().clone();
-            ted::insert_raw(ted::Position::before(start_offset), def.syntax());
-            ted::insert_raw(ted::Position::before(start_offset), &make::tokens::blank_line());
-
-            update_variant(&variant);
+            builder.rewrite(rewriter);
         },
     )
 }
@@ -146,11 +136,34 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Va
         .any(|(name, _)| name.to_string() == variant_name.to_string())
 }
 
-fn create_struct_def(
+fn insert_import(
+    ctx: &AssistContext,
+    rewriter: &mut SyntaxRewriter,
+    scope_node: &SyntaxNode,
+    module: &Module,
+    enum_module_def: &ModuleDef,
+    variant_hir_name: &Name,
+) -> Option<()> {
+    let db = ctx.db();
+    let mod_path =
+        module.find_use_path_prefixed(db, *enum_module_def, ctx.config.insert_use.prefix_kind);
+    if let Some(mut mod_path) = mod_path {
+        mod_path.pop_segment();
+        mod_path.push_segment(variant_hir_name.clone());
+        let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
+        *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use);
+    }
+    Some(())
+}
+
+fn extract_struct_def(
+    rewriter: &mut SyntaxRewriter,
+    enum_: &ast::Enum,
     variant_name: ast::Name,
     field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
+    start_offset: &SyntaxElement,
     visibility: Option<ast::Visibility>,
-) -> ast::Struct {
+) -> Option<()> {
     let pub_vis = Some(make::visibility_pub());
     let field_list = match field_list {
         Either::Left(field_list) => {
@@ -167,90 +180,65 @@ fn create_struct_def(
         .into(),
     };
 
-    make::struct_(visibility, variant_name, None, field_list).clone_for_update()
+    rewriter.insert_before(
+        start_offset,
+        make::struct_(visibility, variant_name, None, field_list).syntax(),
+    );
+    rewriter.insert_before(start_offset, &make::tokens::blank_line());
+
+    if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
+        rewriter
+            .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
+    }
+    Some(())
 }
 
-fn update_variant(variant: &ast::Variant) -> Option<()> {
+fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
     let name = variant.name()?;
     let tuple_field = make::tuple_field(None, make::ty(&name.text()));
     let replacement = make::variant(
         name,
         Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
-    )
-    .clone_for_update();
-    ted::replace(variant.syntax(), replacement.syntax());
-    Some(())
-}
-
-fn apply_references(
-    insert_use_cfg: InsertUseConfig,
-    segment: ast::PathSegment,
-    node: SyntaxNode,
-    import: Option<(ImportScope, hir::ModPath)>,
-) {
-    if let Some((scope, path)) = import {
-        insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg);
-    }
-    ted::insert_raw(
-        ted::Position::before(segment.syntax()),
-        make::path_from_text(&format!("{}", segment)).clone_for_update().syntax(),
     );
-    ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['(']));
-    ted::insert_raw(ted::Position::after(&node), make::token(T![')']));
+    rewriter.replace(variant.syntax(), replacement.syntax());
+    Some(())
 }
 
-fn process_references(
+fn update_reference(
     ctx: &AssistContext,
-    visited_modules: &mut FxHashSet<Module>,
-    source_file: &SyntaxNode,
+    rewriter: &mut SyntaxRewriter,
+    reference: FileReference,
+    source_file: &SourceFile,
     enum_module_def: &ModuleDef,
     variant_hir_name: &Name,
-    refs: Vec<FileReference>,
-) -> Vec<(ast::PathSegment, SyntaxNode, Option<(ImportScope, hir::ModPath)>)> {
-    // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
-    // and corresponding nodes up front
-    refs.into_iter()
-        .flat_map(|reference| {
-            let (segment, scope_node, module) =
-                reference_to_node(&ctx.sema, source_file, reference)?;
-            if !visited_modules.contains(&module) {
-                let mod_path = module.find_use_path_prefixed(
-                    ctx.sema.db,
-                    *enum_module_def,
-                    ctx.config.insert_use.prefix_kind,
-                );
-                if let Some(mut mod_path) = mod_path {
-                    mod_path.pop_segment();
-                    mod_path.push_segment(variant_hir_name.clone());
-                    let scope = ImportScope::find_insert_use_container(&scope_node)?;
-                    visited_modules.insert(module);
-                    return Some((segment, scope_node, Some((scope, mod_path))));
-                }
-            }
-            Some((segment, scope_node, None))
-        })
-        .collect()
-}
-
-fn reference_to_node(
-    sema: &hir::Semantics<RootDatabase>,
-    source_file: &SyntaxNode,
-    reference: FileReference,
-) -> Option<(ast::PathSegment, SyntaxNode, hir::Module)> {
+    visited_modules_set: &mut FxHashSet<Module>,
+) -> Option<()> {
     let offset = reference.range.start();
-    if let Some(path_expr) = find_node_at_offset::<ast::PathExpr>(source_file, offset) {
+    let (segment, expr) = if let Some(path_expr) =
+        find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
+    {
         // tuple variant
-        Some((path_expr.path()?.segment()?, path_expr.syntax().parent()?))
-    } else if let Some(record_expr) = find_node_at_offset::<ast::RecordExpr>(source_file, offset) {
+        (path_expr.path()?.segment()?, path_expr.syntax().parent()?)
+    } else if let Some(record_expr) =
+        find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
+    {
         // record variant
-        Some((record_expr.path()?.segment()?, record_expr.syntax().clone()))
+        (record_expr.path()?.segment()?, record_expr.syntax().clone())
     } else {
-        None
+        return None;
+    };
+
+    let module = ctx.sema.scope(&expr).module()?;
+    if !visited_modules_set.contains(&module) {
+        if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
+        {
+            visited_modules_set.insert(module);
+        }
     }
-    .and_then(|(segment, expr)| {
-        let module = sema.scope(&expr).module()?;
-        Some((segment, expr, module))
-    })
+    rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
+    rewriter.insert_after(segment.syntax(), segment.syntax());
+    rewriter.insert_after(&expr, &make::token(T![')']));
+    Some(())
 }
 
 #[cfg(test)]
@@ -357,7 +345,7 @@ mod my_mod {
 
         pub struct MyField(pub u8, pub u8);
 
-pub enum MyEnum {
+        pub enum MyEnum {
             MyField(MyField),
         }
     }
diff --git a/crates/ide_assists/src/handlers/reorder_fields.rs b/crates/ide_assists/src/handlers/reorder_fields.rs
index e90bbdbcf2..1a95135ca1 100644
--- a/crates/ide_assists/src/handlers/reorder_fields.rs
+++ b/crates/ide_assists/src/handlers/reorder_fields.rs
@@ -83,9 +83,11 @@ fn replace<T: AstNode + PartialEq>(
     fields: impl Iterator<Item = T>,
     sorted_fields: impl IntoIterator<Item = T>,
 ) {
-    fields.zip(sorted_fields).for_each(|(field, sorted_field)| {
-        ted::replace(field.syntax(), sorted_field.syntax().clone_for_update())
-    });
+    fields.zip(sorted_fields).filter(|(field, sorted)| field != sorted).for_each(
+        |(field, sorted_field)| {
+            ted::replace(field.syntax(), sorted_field.syntax().clone_for_update());
+        },
+    );
 }
 
 fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
diff --git a/crates/ide_assists/src/handlers/reorder_impl.rs b/crates/ide_assists/src/handlers/reorder_impl.rs
index 72d8892481..f976e73adc 100644
--- a/crates/ide_assists/src/handlers/reorder_impl.rs
+++ b/crates/ide_assists/src/handlers/reorder_impl.rs
@@ -4,8 +4,9 @@ use rustc_hash::FxHashMap;
 use hir::{PathResolution, Semantics};
 use ide_db::RootDatabase;
 use syntax::{
+    algo,
     ast::{self, NameOwner},
-    ted, AstNode,
+    AstNode,
 };
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -74,16 +75,13 @@ pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
     }
 
     let target = items.syntax().text_range();
-    acc.add(
-        AssistId("reorder_impl", AssistKind::RefactorRewrite),
-        "Sort methods",
-        target,
-        |builder| {
-            methods.into_iter().zip(sorted).for_each(|(old, new)| {
-                ted::replace(builder.make_ast_mut(old).syntax(), new.clone_for_update().syntax())
-            });
-        },
-    )
+    acc.add(AssistId("reorder_impl", AssistKind::RefactorRewrite), "Sort methods", target, |edit| {
+        let mut rewriter = algo::SyntaxRewriter::default();
+        for (old, new) in methods.iter().zip(&sorted) {
+            rewriter.replace(old.syntax(), new.syntax());
+        }
+        edit.rewrite(rewriter);
+    })
 }
 
 fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
index 99ba798606..36d2e0331e 100644
--- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,5 +1,5 @@
 use ide_db::helpers::insert_use::{insert_use, ImportScope};
-use syntax::{ast, match_ast, ted, AstNode, SyntaxNode};
+use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists};
 
@@ -31,7 +31,7 @@ pub(crate) fn replace_qualified_name_with_use(
     }
 
     let target = path.syntax().text_range();
-    let scope = ImportScope::find_insert_use_container_with_macros(path.syntax(), &ctx.sema)?;
+    let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
     let syntax = scope.as_syntax_node();
     acc.add(
         AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
@@ -40,17 +40,18 @@ pub(crate) fn replace_qualified_name_with_use(
         |builder| {
             // Now that we've brought the name into scope, re-qualify all paths that could be
             // affected (that is, all paths inside the node we added the `use` to).
-            let syntax = builder.make_mut(syntax.clone());
+            let mut rewriter = SyntaxRewriter::default();
+            shorten_paths(&mut rewriter, syntax.clone(), &path);
             if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
-                shorten_paths(&syntax, &path.clone_for_update());
-                insert_use(import_scope, path, ctx.config.insert_use);
+                rewriter += insert_use(import_scope, path, ctx.config.insert_use);
+                builder.rewrite(rewriter);
             }
         },
     )
 }
 
 /// Adds replacements to `re` that shorten `path` in all descendants of `node`.
-fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
+fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) {
     for child in node.children() {
         match_ast! {
             match child {
@@ -59,26 +60,34 @@ fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
                 ast::Use(_it) => continue,
                 // Don't descend into submodules, they don't have the same `use` items in scope.
                 ast::Module(_it) => continue,
-                ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
-                    shorten_paths(p.syntax(), path);
+
+                ast::Path(p) => {
+                    match maybe_replace_path(rewriter, p.clone(), path.clone()) {
+                        Some(()) => {},
+                        None => shorten_paths(rewriter, p.syntax().clone(), path),
+                    }
                 },
-                _ => shorten_paths(&child, path),
+                _ => shorten_paths(rewriter, child, path),
             }
         }
     }
 }
 
-fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
+fn maybe_replace_path(
+    rewriter: &mut SyntaxRewriter<'static>,
+    path: ast::Path,
+    target: ast::Path,
+) -> Option<()> {
     if !path_eq(path.clone(), target) {
         return None;
     }
 
     // Shorten `path`, leaving only its last segment.
     if let Some(parent) = path.qualifier() {
-        ted::remove(parent.syntax());
+        rewriter.delete(parent.syntax());
     }
     if let Some(double_colon) = path.coloncolon_token() {
-        ted::remove(&double_colon);
+        rewriter.delete(&double_colon);
     }
 
     Some(())
@@ -141,7 +150,6 @@ Debug
     ",
         );
     }
-
     #[test]
     fn test_replace_add_use_no_anchor_with_item_below() {
         check_assist(
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 5a90ad715b..d67524937e 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -140,8 +140,7 @@ pub fn add_trait_assoc_items_to_impl(
 
     let items = items
         .into_iter()
-        .map(|it| it.clone_for_update())
-        .inspect(|it| ast_transform::apply(&*ast_transform, it))
+        .map(|it| ast_transform::apply(&*ast_transform, it))
         .map(|it| match it {
             ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
             ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs
index 9d5b61562a..8e211ae1ed 100644
--- a/crates/ide_completion/src/completions/flyimport.rs
+++ b/crates/ide_completion/src/completions/flyimport.rs
@@ -132,7 +132,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
 
     let user_input_lowercased = potential_import_name.to_lowercase();
     let import_assets = import_assets(ctx, potential_import_name)?;
-    let import_scope = ImportScope::find_insert_use_container_with_macros(
+    let import_scope = ImportScope::find_insert_use_container(
         position_for_import(ctx, Some(import_assets.import_candidate()))?,
         &ctx.sema,
     )?;
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 99edb94992..16991b6880 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -377,11 +377,11 @@ impl ImportEdit {
     pub fn to_text_edit(&self, cfg: InsertUseConfig) -> Option<TextEdit> {
         let _p = profile::span("ImportEdit::to_text_edit");
 
-        let new_ast = self.scope.clone_for_update();
-        insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), cfg);
+        let rewriter =
+            insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg);
+        let old_ast = rewriter.rewrite_root()?;
         let mut import_insert = TextEdit::builder();
-        algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node())
-            .into_text_edit(&mut import_insert);
+        algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert);
 
         Some(import_insert.finish())
     }
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs
index e326335654..6f3d5c5c57 100644
--- a/crates/ide_completion/src/lib.rs
+++ b/crates/ide_completion/src/lib.rs
@@ -179,7 +179,7 @@ pub fn resolve_completion_edits(
 ) -> Option<Vec<TextEdit>> {
     let ctx = CompletionContext::new(db, position, config)?;
     let position_for_import = position_for_import(&ctx, None)?;
-    let scope = ImportScope::find_insert_use_container_with_macros(position_for_import, &ctx.sema)?;
+    let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?;
 
     let current_module = ctx.sema.scope(position_for_import).module()?;
     let current_crate = current_module.krate();
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index a43504a275..be3a22725a 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -4,9 +4,13 @@ use std::{cmp::Ordering, iter::successors};
 use hir::Semantics;
 use itertools::{EitherOrBoth, Itertools};
 use syntax::{
-    algo,
-    ast::{self, edit::AstNodeEdit, make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner},
-    ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
+    algo::SyntaxRewriter,
+    ast::{
+        self,
+        edit::{AstNodeEdit, IndentLevel},
+        make, AstNode, AttrsOwner, PathSegmentKind, VisibilityOwner,
+    },
+    AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
 };
 
 use crate::RootDatabase;
@@ -38,18 +42,13 @@ impl ImportScope {
     }
 
     /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
-    pub fn find_insert_use_container_with_macros(
+    pub fn find_insert_use_container(
         position: &SyntaxNode,
         sema: &Semantics<'_, RootDatabase>,
     ) -> Option<Self> {
         sema.ancestors_with_macros(position.clone()).find_map(Self::from)
     }
 
-    /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
-    pub fn find_insert_use_container(position: &SyntaxNode) -> Option<Self> {
-        std::iter::successors(Some(position.clone()), SyntaxNode::parent).find_map(Self::from)
-    }
-
     pub fn as_syntax_node(&self) -> &SyntaxNode {
         match self {
             ImportScope::File(file) => file.syntax(),
@@ -57,32 +56,127 @@ impl ImportScope {
         }
     }
 
-    pub fn clone_for_update(&self) -> Self {
+    fn indent_level(&self) -> IndentLevel {
+        match self {
+            ImportScope::File(file) => file.indent_level(),
+            ImportScope::Module(item_list) => item_list.indent_level() + 1,
+        }
+    }
+
+    fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
         match self {
-            ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
-            ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
+            ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
+            // don't insert the imports before the item list's opening curly brace
+            ImportScope::Module(item_list) => item_list
+                .l_curly_token()
+                .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
+                .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
         }
     }
+
+    fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
+        self.as_syntax_node()
+            .children_with_tokens()
+            .filter(|child| match child {
+                NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
+                NodeOrToken::Token(token) => is_inner_comment(token.clone()),
+            })
+            .last()
+            .map(|last_inner_element| {
+                (InsertPosition::After(last_inner_element), AddBlankLine::BeforeTwice)
+            })
+            .unwrap_or_else(|| self.first_insert_pos())
+    }
+}
+
+fn is_inner_attribute(node: SyntaxNode) -> bool {
+    ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
+}
+
+fn is_inner_comment(token: SyntaxToken) -> bool {
+    ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
+        == Some(ast::CommentPlacement::Inner)
 }
 
 /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
-pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
+pub fn insert_use<'a>(
+    scope: &ImportScope,
+    path: ast::Path,
+    cfg: InsertUseConfig,
+) -> SyntaxRewriter<'a> {
     let _p = profile::span("insert_use");
-    let use_item =
-        make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
+    let mut rewriter = SyntaxRewriter::default();
+    let use_item = make::use_(None, make::use_tree(path.clone(), None, None, false));
     // merge into existing imports if possible
     if let Some(mb) = cfg.merge {
         for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
             if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
-                ted::replace(existing_use.syntax(), merged.syntax());
-                return;
+                rewriter.replace(existing_use.syntax(), merged.syntax());
+                return rewriter;
             }
         }
     }
 
     // either we weren't allowed to merge or there is no import that fits the merge conditions
     // so look for the place we have to insert to
-    insert_use_(scope, path, cfg.group, use_item);
+    let (insert_position, add_blank) = find_insert_position(scope, path, cfg.group);
+
+    let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
+        Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
+    } else {
+        None
+    };
+
+    let to_insert: Vec<SyntaxElement> = {
+        let mut buf = Vec::new();
+
+        match add_blank {
+            AddBlankLine::Before | AddBlankLine::Around => {
+                buf.push(make::tokens::single_newline().into())
+            }
+            AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
+            _ => (),
+        }
+
+        if add_blank.has_before() {
+            if let Some(indent) = indent.clone() {
+                cov_mark::hit!(insert_use_indent_before);
+                buf.push(indent);
+            }
+        }
+
+        buf.push(use_item.syntax().clone().into());
+
+        match add_blank {
+            AddBlankLine::After | AddBlankLine::Around => {
+                buf.push(make::tokens::single_newline().into())
+            }
+            AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
+            _ => (),
+        }
+
+        // only add indentation *after* our stuff if there's another node directly after it
+        if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) {
+            if let Some(indent) = indent {
+                cov_mark::hit!(insert_use_indent_after);
+                buf.push(indent);
+            }
+        } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) {
+            cov_mark::hit!(insert_use_no_indent_after);
+        }
+
+        buf
+    };
+
+    match insert_position {
+        InsertPosition::First => {
+            rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
+        }
+        InsertPosition::Last => return rewriter, // actually unreachable
+        InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
+        InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
+    }
+    rewriter
 }
 
 fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
@@ -141,7 +235,7 @@ pub fn try_merge_trees(
     } else {
         (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
     };
-    recursive_merge(&lhs, &rhs, merge)
+    recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update())
 }
 
 /// Recursively "zips" together lhs and rhs.
@@ -240,12 +334,7 @@ fn recursive_merge(
             }
         }
     }
-
-    Some(if let Some(old) = lhs.use_tree_list() {
-        lhs.replace_descendant(old, make::use_tree_list(use_trees)).clone_for_update()
-    } else {
-        lhs.clone()
-    })
+    Some(lhs.with_use_tree_list(make::use_tree_list(use_trees)))
 }
 
 /// Traverses both paths until they differ, returning the common prefix of both.
@@ -431,15 +520,32 @@ impl ImportGroup {
     }
 }
 
-fn insert_use_(
+#[derive(PartialEq, Eq)]
+enum AddBlankLine {
+    Before,
+    BeforeTwice,
+    Around,
+    After,
+    AfterTwice,
+}
+
+impl AddBlankLine {
+    fn has_before(&self) -> bool {
+        matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around)
+    }
+    fn has_after(&self) -> bool {
+        matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around)
+    }
+}
+
+fn find_insert_position(
     scope: &ImportScope,
     insert_path: ast::Path,
     group_imports: bool,
-    use_item: ast::Use,
-) {
-    let scope_syntax = scope.as_syntax_node();
+) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
     let group = ImportGroup::new(&insert_path);
-    let path_node_iter = scope_syntax
+    let path_node_iter = scope
+        .as_syntax_node()
         .children()
         .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
         .flat_map(|(use_, node)| {
@@ -451,14 +557,9 @@ fn insert_use_(
 
     if !group_imports {
         if let Some((_, _, node)) = path_node_iter.last() {
-            cov_mark::hit!(insert_no_grouping_last);
-            ted::insert(ted::Position::after(node), use_item.syntax());
-        } else {
-            cov_mark::hit!(insert_no_grouping_last2);
-            ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
-            ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
+            return (InsertPosition::After(node.into()), AddBlankLine::Before);
         }
-        return;
+        return (InsertPosition::First, AddBlankLine::AfterTwice);
     }
 
     // Iterator that discards anything thats not in the required grouping
@@ -471,91 +572,43 @@ fn insert_use_(
     // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
     let mut last = None;
     // find the element that would come directly after our new import
-    let post_insert: Option<(_, _, SyntaxNode)> = group_iter
-        .inspect(|(.., node)| last = Some(node.clone()))
-        .find(|&(ref path, has_tl, _)| {
+    let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find(
+        |&(ref path, has_tl, _)| {
             use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
-        });
+        },
+    );
 
-    if let Some((.., node)) = post_insert {
-        cov_mark::hit!(insert_group);
+    match post_insert {
         // insert our import before that element
-        return ted::insert(ted::Position::before(node), use_item.syntax());
-    }
-    if let Some(node) = last {
-        cov_mark::hit!(insert_group_last);
+        Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
         // there is no element after our new import, so append it to the end of the group
-        return ted::insert(ted::Position::after(node), use_item.syntax());
-    }
-
-    // the group we were looking for actually doesn't exist, so insert
-
-    let mut last = None;
-    // find the group that comes after where we want to insert
-    let post_group = path_node_iter
-        .inspect(|(.., node)| last = Some(node.clone()))
-        .find(|(p, ..)| ImportGroup::new(p) > group);
-    if let Some((.., node)) = post_group {
-        cov_mark::hit!(insert_group_new_group);
-        ted::insert(ted::Position::before(&node), use_item.syntax());
-        if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
-            ted::insert(ted::Position::after(node), make::tokens::single_newline());
-        }
-        return;
-    }
-    // there is no such group, so append after the last one
-    if let Some(node) = last {
-        cov_mark::hit!(insert_group_no_group);
-        ted::insert(ted::Position::after(&node), use_item.syntax());
-        ted::insert(ted::Position::after(node), make::tokens::single_newline());
-        return;
-    }
-    // there are no imports in this file at all
-    if let Some(last_inner_element) = scope_syntax
-        .children_with_tokens()
-        .filter(|child| match child {
-            NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
-            NodeOrToken::Token(token) => is_inner_comment(token.clone()),
-        })
-        .last()
-    {
-        cov_mark::hit!(insert_group_empty_inner_attr);
-        ted::insert(ted::Position::after(&last_inner_element), use_item.syntax());
-        ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
-        return;
-    }
-    match scope {
-        ImportScope::File(_) => {
-            cov_mark::hit!(insert_group_empty_file);
-            ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
-            ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax())
-        }
-        // don't insert the imports before the item list's opening curly brace
-        ImportScope::Module(item_list) => match item_list.l_curly_token() {
-            Some(b) => {
-                cov_mark::hit!(insert_group_empty_module);
-                ted::insert(ted::Position::after(&b), make::tokens::single_newline());
-                ted::insert(ted::Position::after(&b), use_item.syntax());
-            }
+        None => match last {
+            Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
+            // the group we were looking for actually doesnt exist, so insert
             None => {
-                // This should never happens, broken module syntax node
-                ted::insert(
-                    ted::Position::first_child_of(scope_syntax),
-                    make::tokens::blank_line(),
-                );
-                ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
+                // similar concept here to the `last` from above
+                let mut last = None;
+                // find the group that comes after where we want to insert
+                let post_group = path_node_iter
+                    .inspect(|(.., node)| last = Some(node.clone()))
+                    .find(|(p, ..)| ImportGroup::new(p) > group);
+                match post_group {
+                    Some((.., node)) => {
+                        (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
+                    }
+                    // there is no such group, so append after the last one
+                    None => match last {
+                        Some(node) => {
+                            (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
+                        }
+                        // there are no imports in this file at all
+                        None => scope.insert_pos_after_last_inner_element(),
+                    },
+                }
             }
         },
     }
 }
 
-fn is_inner_attribute(node: SyntaxNode) -> bool {
-    ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
-}
-
-fn is_inner_comment(token: SyntaxToken) -> bool {
-    ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
-        == Some(ast::CommentPlacement::Inner)
-}
 #[cfg(test)]
 mod tests;
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index c9229c4e07..a153a9e1c3 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -342,10 +342,10 @@ enum InsertPos {
 
 #[derive(Default)]
 pub struct SyntaxRewriter<'a> {
+    f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
     //FIXME: add debug_assertions that all elements are in fact from the same file.
     replacements: FxHashMap<SyntaxElement, Replacement>,
     insertions: IndexMap<InsertPos, Vec<SyntaxElement>>,
-    _pd: std::marker::PhantomData<&'a ()>,
 }
 
 impl fmt::Debug for SyntaxRewriter<'_> {
@@ -357,7 +357,14 @@ impl fmt::Debug for SyntaxRewriter<'_> {
     }
 }
 
-impl SyntaxRewriter<'_> {
+impl<'a> SyntaxRewriter<'a> {
+    pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
+        SyntaxRewriter {
+            f: Some(Box::new(f)),
+            replacements: FxHashMap::default(),
+            insertions: IndexMap::default(),
+        }
+    }
     pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
         let what = what.clone().into();
         let replacement = Replacement::Delete;
@@ -463,7 +470,7 @@ impl SyntaxRewriter<'_> {
     pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
         let _p = profile::span("rewrite");
 
-        if self.replacements.is_empty() && self.insertions.is_empty() {
+        if self.f.is_none() && self.replacements.is_empty() && self.insertions.is_empty() {
             return node.clone();
         }
         let green = self.rewrite_children(node);
@@ -488,6 +495,7 @@ impl SyntaxRewriter<'_> {
             }
         }
 
+        assert!(self.f.is_none());
         self.replacements
             .keys()
             .filter_map(element_to_node_or_parent)
@@ -502,6 +510,10 @@ impl SyntaxRewriter<'_> {
     }
 
     fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> {
+        if let Some(f) = &self.f {
+            assert!(self.replacements.is_empty());
+            return f(element).map(Replacement::Single);
+        }
         self.replacements.get(element).cloned()
     }
 
@@ -562,6 +574,7 @@ fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row
 
 impl ops::AddAssign for SyntaxRewriter<'_> {
     fn add_assign(&mut self, rhs: SyntaxRewriter) {
+        assert!(rhs.f.is_none());
         self.replacements.extend(rhs.replacements);
         for (pos, insertions) in rhs.insertions.into_iter() {
             match self.insertions.entry(pos) {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 42da096061..222b7e212b 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -632,7 +632,6 @@ pub mod tokens {
         SOURCE_FILE
             .tree()
             .syntax()
-            .clone_for_update()
             .descendants_with_tokens()
             .filter_map(|it| it.into_token())
             .find(|it| it.kind() == WHITESPACE && it.text() == "\n\n")
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index 91a06101f5..450f2e447a 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -7,7 +7,7 @@ use std::{mem, ops::RangeInclusive};
 use parser::T;
 
 use crate::{
-    ast::{self, edit::IndentLevel, make, AstNode},
+    ast::{edit::IndentLevel, make},
     SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
 };
 
@@ -147,16 +147,6 @@ pub fn append_child_raw(node: &(impl Into<SyntaxNode> + Clone), child: impl Elem
 fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
     let prev = match &position.repr {
         PositionRepr::FirstChild(_) => return None,
-        PositionRepr::After(it) if it.kind() == SyntaxKind::L_CURLY => {
-            if new.kind() == SyntaxKind::USE {
-                if let Some(item_list) = it.parent().and_then(ast::ItemList::cast) {
-                    let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into());
-                    indent.0 += 1;
-                    return Some(make::tokens::whitespace(&format!("\n{}", indent)));
-                }
-            }
-            it
-        }
         PositionRepr::After(it) => it,
     };
     ws_between(prev, new)
@@ -183,10 +173,7 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
     }
 
     if right.kind() == SyntaxKind::USE {
-        let mut indent = IndentLevel::from_element(left);
-        if left.kind() == SyntaxKind::USE {
-            indent.0 = IndentLevel::from_element(right).0.max(indent.0);
-        }
+        let indent = IndentLevel::from_element(left);
         return Some(make::tokens::whitespace(&format!("\n{}", indent)));
     }
     Some(make::tokens::single_space())
diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs
index 663ca132c4..9f2609d11d 100644
--- a/xtask/src/dist.rs
+++ b/xtask/src/dist.rs
@@ -41,7 +41,7 @@ fn dist_client(version: &str, release_tag: &str) -> Result<()> {
         .replace(r#""version": "0.4.0-dev""#, &format!(r#""version": "{}""#, version))
         .replace(r#""releaseTag": null"#, &format!(r#""releaseTag": "{}""#, release_tag))
         .replace(r#""$generated-start": false,"#, "")
-        .replace(r#""$generated-end": false"#, "");
+        .replace(",\n        \"$generated-end\": false", "");
 
     if nightly {
         patch.replace(


Test patch:
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 048c213e22..3d151e629d 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -5,7 +5,6 @@ use test_utils::assert_eq_text;
 
 #[test]
 fn insert_not_group() {
-    cov_mark::check!(insert_no_grouping_last);
     check(
         "use external_crate2::bar::A",
         r"
@@ -27,21 +26,6 @@ use external_crate2::bar::A;",
     );
 }
 
-#[test]
-fn insert_not_group_empty() {
-    cov_mark::check!(insert_no_grouping_last2);
-    check(
-        "use external_crate2::bar::A",
-        r"",
-        r"use external_crate2::bar::A;
-
-",
-        None,
-        false,
-        false,
-    );
-}
-
 #[test]
 fn insert_existing() {
     check_full("std::fs", "use std::fs;", "use std::fs;")
@@ -67,21 +51,21 @@ use std::bar::G;",
 
 #[test]
 fn insert_start_indent() {
+    cov_mark::check!(insert_use_indent_after);
     check_none(
         "std::bar::AA",
         r"
     use std::bar::B;
-    use std::bar::C;",
+    use std::bar::D;",
         r"
     use std::bar::AA;
     use std::bar::B;
-    use std::bar::C;",
-    );
+    use std::bar::D;",
+    )
 }
 
 #[test]
 fn insert_middle() {
-    cov_mark::check!(insert_group);
     check_none(
         "std::bar::EE",
         r"
@@ -118,7 +102,6 @@ fn insert_middle_indent() {
 
 #[test]
 fn insert_end() {
-    cov_mark::check!(insert_group_last);
     check_none(
         "std::bar::ZZ",
         r"
@@ -137,6 +120,7 @@ use std::bar::ZZ;",
 
 #[test]
 fn insert_end_indent() {
+    cov_mark::check!(insert_use_indent_before);
     check_none(
         "std::bar::ZZ",
         r"
@@ -217,7 +201,6 @@ fn insert_first_matching_group() {
 
 #[test]
 fn insert_missing_group_std() {
-    cov_mark::check!(insert_group_new_group);
     check_none(
         "std::fmt",
         r"
@@ -233,7 +216,6 @@ fn insert_missing_group_std() {
 
 #[test]
 fn insert_missing_group_self() {
-    cov_mark::check!(insert_group_no_group);
     check_none(
         "self::fmt",
         r"
@@ -260,7 +242,6 @@ fn main() {}",
 
 #[test]
 fn insert_empty_file() {
-    cov_mark::check!(insert_group_empty_file);
     // empty files will get two trailing newlines
     // this is due to the test case insert_no_imports above
     check_full(
@@ -274,7 +255,7 @@ fn insert_empty_file() {
 
 #[test]
 fn insert_empty_module() {
-    cov_mark::check!(insert_group_empty_module);
+    cov_mark::check!(insert_use_no_indent_after);
     check(
         "foo::bar",
         "mod x {}",
@@ -289,7 +270,6 @@ fn insert_empty_module() {
 
 #[test]
 fn insert_after_inner_attr() {
-    cov_mark::check!(insert_group_empty_inner_attr);
     check_full(
         "foo::bar",
         r"#![allow(unused_imports)]",
@@ -635,7 +615,7 @@ fn check(
     if module {
         syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone();
     }
-    let file = super::ImportScope::from(syntax.clone_for_update()).unwrap();
+    let file = super::ImportScope::from(syntax).unwrap();
     let path = ast::SourceFile::parse(&format!("use {};", path))
         .tree()
         .syntax()
@@ -643,8 +623,12 @@ fn check(
         .find_map(ast::Path::cast)
         .unwrap();
 
-    insert_use(&file, path, InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group });
-    let result = file.as_syntax_node().to_string();
+    let rewriter = insert_use(
+        &file,
+        path,
+        InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group },
+    );
+    let result = rewriter.rewrite(file.as_syntax_node()).to_string();
     assert_eq_text!(ra_fixture_after, &result);
 }