diff --git a/crates/ide-assists/src/assist_context.rs b/crates/ide-assists/src/assist_context.rs
index f9b42661425..8c7670e0cb7 100644
--- a/crates/ide-assists/src/assist_context.rs
+++ b/crates/ide-assists/src/assist_context.rs
@@ -1,28 +1,20 @@
 //! See [`AssistContext`].
 
-use std::mem;
-
 use hir::Semantics;
-use ide_db::{
-    base_db::{AnchoredPathBuf, FileId, FileRange},
-    SnippetCap,
-};
-use ide_db::{
-    label::Label,
-    source_change::{FileSystemEdit, SourceChange},
-    RootDatabase,
-};
+use ide_db::base_db::{FileId, FileRange};
+use ide_db::{label::Label, RootDatabase};
 use syntax::{
     algo::{self, find_node_at_offset, find_node_at_range},
-    AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxNodePtr,
-    SyntaxToken, TextRange, TextSize, TokenAtOffset,
+    AstNode, AstToken, Direction, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange,
+    TextSize, TokenAtOffset,
 };
-use text_edit::{TextEdit, TextEditBuilder};
 
 use crate::{
     assist_config::AssistConfig, Assist, AssistId, AssistKind, AssistResolveStrategy, GroupLabel,
 };
 
+pub(crate) use ide_db::source_change::{SourceChangeBuilder, TreeMutator};
+
 /// `AssistContext` allows to apply an assist or check if it could be applied.
 ///
 /// Assists use a somewhat over-engineered approach, given the current needs.
@@ -163,7 +155,7 @@ impl Assists {
         id: AssistId,
         label: impl Into<String>,
         target: TextRange,
-        f: impl FnOnce(&mut AssistBuilder),
+        f: impl FnOnce(&mut SourceChangeBuilder),
     ) -> Option<()> {
         let mut f = Some(f);
         self.add_impl(None, id, label.into(), target, &mut |it| f.take().unwrap()(it))
@@ -175,7 +167,7 @@ impl Assists {
         id: AssistId,
         label: impl Into<String>,
         target: TextRange,
-        f: impl FnOnce(&mut AssistBuilder),
+        f: impl FnOnce(&mut SourceChangeBuilder),
     ) -> Option<()> {
         let mut f = Some(f);
         self.add_impl(Some(group), id, label.into(), target, &mut |it| f.take().unwrap()(it))
@@ -187,7 +179,7 @@ impl Assists {
         id: AssistId,
         label: String,
         target: TextRange,
-        f: &mut dyn FnMut(&mut AssistBuilder),
+        f: &mut dyn FnMut(&mut SourceChangeBuilder),
     ) -> Option<()> {
         if !self.is_allowed(&id) {
             return None;
@@ -195,7 +187,7 @@ impl Assists {
 
         let mut trigger_signature_help = false;
         let source_change = if self.resolve.should_resolve(&id) {
-            let mut builder = AssistBuilder::new(self.file);
+            let mut builder = SourceChangeBuilder::new(self.file);
             f(&mut builder);
             trigger_signature_help = builder.trigger_signature_help;
             Some(builder.finish())
@@ -216,132 +208,3 @@ impl Assists {
         }
     }
 }
-
-pub(crate) struct AssistBuilder {
-    edit: TextEditBuilder,
-    file_id: FileId,
-    source_change: SourceChange,
-    trigger_signature_help: bool,
-
-    /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
-    mutated_tree: Option<TreeMutator>,
-}
-
-pub(crate) struct TreeMutator {
-    immutable: SyntaxNode,
-    mutable_clone: SyntaxNode,
-}
-
-impl TreeMutator {
-    pub(crate) fn new(immutable: &SyntaxNode) -> TreeMutator {
-        let immutable = immutable.ancestors().last().unwrap();
-        let mutable_clone = immutable.clone_for_update();
-        TreeMutator { immutable, mutable_clone }
-    }
-
-    pub(crate) fn make_mut<N: AstNode>(&self, node: &N) -> N {
-        N::cast(self.make_syntax_mut(node.syntax())).unwrap()
-    }
-
-    pub(crate) fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode {
-        let ptr = SyntaxNodePtr::new(node);
-        ptr.to_node(&self.mutable_clone)
-    }
-}
-
-impl AssistBuilder {
-    pub(crate) fn new(file_id: FileId) -> AssistBuilder {
-        AssistBuilder {
-            edit: TextEdit::builder(),
-            file_id,
-            source_change: SourceChange::default(),
-            trigger_signature_help: false,
-            mutated_tree: None,
-        }
-    }
-
-    pub(crate) fn edit_file(&mut self, file_id: FileId) {
-        self.commit();
-        self.file_id = file_id;
-    }
-
-    fn commit(&mut self) {
-        if let Some(tm) = self.mutated_tree.take() {
-            algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit)
-        }
-
-        let edit = mem::take(&mut self.edit).finish();
-        if !edit.is_empty() {
-            self.source_change.insert_source_edit(self.file_id, edit);
-        }
-    }
-
-    pub(crate) fn make_mut<N: AstNode>(&mut self, node: N) -> N {
-        self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
-    }
-    /// Returns a copy of the `node`, suitable for mutation.
-    ///
-    /// Syntax trees in rust-analyzer are typically immutable, and mutating
-    /// operations panic at runtime. However, it is possible to make a copy of
-    /// the tree and mutate the copy freely. Mutation is based on interior
-    /// mutability, and different nodes in the same tree see the same mutations.
-    ///
-    /// The typical pattern for an assist is to find specific nodes in the read
-    /// phase, and then get their mutable couterparts using `make_mut` in the
-    /// mutable state.
-    pub(crate) fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode {
-        self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node)
-    }
-
-    /// Remove specified `range` of text.
-    pub(crate) fn delete(&mut self, range: TextRange) {
-        self.edit.delete(range)
-    }
-    /// Append specified `text` at the given `offset`
-    pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
-        self.edit.insert(offset, text.into())
-    }
-    /// Append specified `snippet` at the given `offset`
-    pub(crate) fn insert_snippet(
-        &mut self,
-        _cap: SnippetCap,
-        offset: TextSize,
-        snippet: impl Into<String>,
-    ) {
-        self.source_change.is_snippet = true;
-        self.insert(offset, snippet);
-    }
-    /// Replaces specified `range` of text with a given string.
-    pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
-        self.edit.replace(range, replace_with.into())
-    }
-    /// Replaces specified `range` of text with a given `snippet`.
-    pub(crate) fn replace_snippet(
-        &mut self,
-        _cap: SnippetCap,
-        range: TextRange,
-        snippet: impl Into<String>,
-    ) {
-        self.source_change.is_snippet = true;
-        self.replace(range, snippet);
-    }
-    pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
-        algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
-    }
-    pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
-        let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
-        self.source_change.push_file_system_edit(file_system_edit);
-    }
-    pub(crate) fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) {
-        let file_system_edit = FileSystemEdit::MoveFile { src, dst };
-        self.source_change.push_file_system_edit(file_system_edit);
-    }
-    pub(crate) fn trigger_signature_help(&mut self) {
-        self.trigger_signature_help = true;
-    }
-
-    fn finish(mut self) -> SourceChange {
-        self.commit();
-        mem::take(&mut self.source_change)
-    }
-}
diff --git a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
index 4ab8e93a290..d8f52270846 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -5,7 +5,7 @@ use syntax::{
     match_ast, SyntaxNode,
 };
 
-use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
 
 // Assist: convert_tuple_struct_to_named_struct
 //
@@ -80,7 +80,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct(
 
 fn edit_struct_def(
     ctx: &AssistContext<'_>,
-    edit: &mut AssistBuilder,
+    edit: &mut SourceChangeBuilder,
     strukt: &Either<ast::Struct, ast::Variant>,
     tuple_fields: ast::TupleFieldList,
     names: Vec<ast::Name>,
@@ -122,7 +122,7 @@ fn edit_struct_def(
 
 fn edit_struct_references(
     ctx: &AssistContext<'_>,
-    edit: &mut AssistBuilder,
+    edit: &mut SourceChangeBuilder,
     strukt: Either<hir::Struct, hir::Variant>,
     names: &[ast::Name],
 ) {
@@ -132,7 +132,7 @@ fn edit_struct_references(
     };
     let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
 
-    let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
+    let edit_node = |edit: &mut SourceChangeBuilder, node: SyntaxNode| -> Option<()> {
         match_ast! {
             match node {
                 ast::TupleStructPat(tuple_struct_pat) => {
@@ -203,7 +203,7 @@ fn edit_struct_references(
 
 fn edit_field_references(
     ctx: &AssistContext<'_>,
-    edit: &mut AssistBuilder,
+    edit: &mut SourceChangeBuilder,
     fields: impl Iterator<Item = ast::TupleField>,
     names: &[ast::Name],
 ) {
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index c1f57532bb2..dc581ff3bd2 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -8,7 +8,7 @@ use syntax::{
     TextRange,
 };
 
-use crate::assist_context::{AssistBuilder, AssistContext, Assists};
+use crate::assist_context::{AssistContext, Assists, SourceChangeBuilder};
 
 // Assist: destructure_tuple_binding
 //
@@ -151,7 +151,7 @@ struct TupleData {
 }
 fn edit_tuple_assignment(
     ctx: &AssistContext<'_>,
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     data: &TupleData,
     in_sub_pattern: bool,
 ) {
@@ -195,7 +195,7 @@ fn edit_tuple_assignment(
 
 fn edit_tuple_usages(
     data: &TupleData,
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     ctx: &AssistContext<'_>,
     in_sub_pattern: bool,
 ) {
@@ -211,7 +211,7 @@ fn edit_tuple_usages(
 }
 fn edit_tuple_usage(
     ctx: &AssistContext<'_>,
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     usage: &FileReference,
     data: &TupleData,
     in_sub_pattern: bool,
@@ -239,7 +239,7 @@ fn edit_tuple_usage(
 
 fn edit_tuple_field_usage(
     ctx: &AssistContext<'_>,
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     data: &TupleData,
     index: TupleIndex,
 ) {
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 a93648f2d31..dfb56521264 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
@@ -20,7 +20,7 @@ use syntax::{
     SyntaxNode, T,
 };
 
-use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
 
 // Assist: extract_struct_from_enum_variant
 //
@@ -374,7 +374,7 @@ fn apply_references(
 
 fn process_references(
     ctx: &AssistContext<'_>,
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     visited_modules: &mut FxHashSet<Module>,
     enum_module_def: &ModuleDef,
     variant_hir_name: &Name,
diff --git a/crates/ide-assists/src/handlers/generate_deref.rs b/crates/ide-assists/src/handlers/generate_deref.rs
index b9637ee8d7c..b484635121e 100644
--- a/crates/ide-assists/src/handlers/generate_deref.rs
+++ b/crates/ide-assists/src/handlers/generate_deref.rs
@@ -8,7 +8,7 @@ use syntax::{
 };
 
 use crate::{
-    assist_context::{AssistBuilder, AssistContext, Assists},
+    assist_context::{AssistContext, Assists, SourceChangeBuilder},
     utils::generate_trait_impl_text,
     AssistId, AssistKind,
 };
@@ -120,7 +120,7 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()
 }
 
 fn generate_edit(
-    edit: &mut AssistBuilder,
+    edit: &mut SourceChangeBuilder,
     strukt: ast::Struct,
     field_type_syntax: &SyntaxNode,
     field_name: impl Display,
diff --git a/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
index ce91dd23703..2fc754e3e50 100644
--- a/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ide-assists/src/handlers/introduce_named_lifetime.rs
@@ -5,7 +5,7 @@ use syntax::{
     AstNode, TextRange,
 };
 
-use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
+use crate::{assist_context::SourceChangeBuilder, AssistContext, AssistId, AssistKind, Assists};
 
 static ASSIST_NAME: &str = "introduce_named_lifetime";
 static ASSIST_LABEL: &str = "Introduce named lifetime";
@@ -140,7 +140,7 @@ enum NeedsLifetime {
 }
 
 impl NeedsLifetime {
-    fn make_mut(self, builder: &mut AssistBuilder) -> Self {
+    fn make_mut(self, builder: &mut SourceChangeBuilder) -> Self {
         match self {
             Self::SelfParam(it) => Self::SelfParam(builder.make_mut(it)),
             Self::RefType(it) => Self::RefType(builder.make_mut(it)),
diff --git a/crates/ide-assists/src/handlers/remove_unused_param.rs b/crates/ide-assists/src/handlers/remove_unused_param.rs
index 59ea94ea1ff..bd2e8fbe389 100644
--- a/crates/ide-assists/src/handlers/remove_unused_param.rs
+++ b/crates/ide-assists/src/handlers/remove_unused_param.rs
@@ -8,7 +8,8 @@ use syntax::{
 use SyntaxKind::WHITESPACE;
 
 use crate::{
-    assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
+    assist_context::SourceChangeBuilder, utils::next_prev, AssistContext, AssistId, AssistKind,
+    Assists,
 };
 
 // Assist: remove_unused_param
@@ -88,7 +89,7 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) ->
 
 fn process_usages(
     ctx: &AssistContext<'_>,
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     file_id: FileId,
     references: Vec<FileReference>,
     arg_to_remove: usize,
diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
index bd50208da5f..d139f78a6f3 100644
--- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -10,7 +10,7 @@ use syntax::{
 };
 
 use crate::{
-    assist_context::{AssistBuilder, AssistContext, Assists},
+    assist_context::{AssistContext, Assists, SourceChangeBuilder},
     utils::{
         add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body,
         generate_trait_impl_text, render_snippet, Cursor, DefaultMethods,
@@ -224,7 +224,7 @@ fn impl_def_from_trait(
 }
 
 fn update_attribute(
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     old_derives: &[ast::Path],
     old_tree: &ast::TokenTree,
     old_trait_path: &ast::Path,
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index 3e61d0741d3..103e3259fa2 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -20,7 +20,7 @@ use syntax::{
     SyntaxNode, TextRange, TextSize, T,
 };
 
-use crate::assist_context::{AssistBuilder, AssistContext};
+use crate::assist_context::{AssistContext, SourceChangeBuilder};
 
 pub(crate) mod suggest_name;
 mod gen_trait_fn_body;
@@ -484,7 +484,7 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str
 }
 
 pub(crate) fn add_method_to_adt(
-    builder: &mut AssistBuilder,
+    builder: &mut SourceChangeBuilder,
     adt: &ast::Adt,
     impl_def: Option<ast::Impl>,
     method: &str,
diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs
index 8132c73ef26..21314ad74ef 100644
--- a/crates/ide-db/src/source_change.rs
+++ b/crates/ide-db/src/source_change.rs
@@ -3,12 +3,15 @@
 //!
 //! It can be viewed as a dual for `Change`.
 
-use std::{collections::hash_map::Entry, iter};
+use std::{collections::hash_map::Entry, iter, mem};
 
 use base_db::{AnchoredPathBuf, FileId};
 use rustc_hash::FxHashMap;
 use stdx::never;
-use text_edit::TextEdit;
+use syntax::{algo, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
+use text_edit::{TextEdit, TextEditBuilder};
+
+use crate::SnippetCap;
 
 #[derive(Default, Debug, Clone)]
 pub struct SourceChange {
@@ -81,6 +84,135 @@ impl From<FxHashMap<FileId, TextEdit>> for SourceChange {
     }
 }
 
+pub struct SourceChangeBuilder {
+    pub edit: TextEditBuilder,
+    pub file_id: FileId,
+    pub source_change: SourceChange,
+    pub trigger_signature_help: bool,
+
+    /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
+    pub mutated_tree: Option<TreeMutator>,
+}
+
+pub struct TreeMutator {
+    immutable: SyntaxNode,
+    mutable_clone: SyntaxNode,
+}
+
+impl TreeMutator {
+    pub fn new(immutable: &SyntaxNode) -> TreeMutator {
+        let immutable = immutable.ancestors().last().unwrap();
+        let mutable_clone = immutable.clone_for_update();
+        TreeMutator { immutable, mutable_clone }
+    }
+
+    pub fn make_mut<N: AstNode>(&self, node: &N) -> N {
+        N::cast(self.make_syntax_mut(node.syntax())).unwrap()
+    }
+
+    pub fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode {
+        let ptr = SyntaxNodePtr::new(node);
+        ptr.to_node(&self.mutable_clone)
+    }
+}
+
+impl SourceChangeBuilder {
+    pub fn new(file_id: FileId) -> SourceChangeBuilder {
+        SourceChangeBuilder {
+            edit: TextEdit::builder(),
+            file_id,
+            source_change: SourceChange::default(),
+            trigger_signature_help: false,
+            mutated_tree: None,
+        }
+    }
+
+    pub fn edit_file(&mut self, file_id: FileId) {
+        self.commit();
+        self.file_id = file_id;
+    }
+
+    fn commit(&mut self) {
+        if let Some(tm) = self.mutated_tree.take() {
+            algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit)
+        }
+
+        let edit = mem::take(&mut self.edit).finish();
+        if !edit.is_empty() {
+            self.source_change.insert_source_edit(self.file_id, edit);
+        }
+    }
+
+    pub fn make_mut<N: AstNode>(&mut self, node: N) -> N {
+        self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node)
+    }
+    /// Returns a copy of the `node`, suitable for mutation.
+    ///
+    /// Syntax trees in rust-analyzer are typically immutable, and mutating
+    /// operations panic at runtime. However, it is possible to make a copy of
+    /// the tree and mutate the copy freely. Mutation is based on interior
+    /// mutability, and different nodes in the same tree see the same mutations.
+    ///
+    /// The typical pattern for an assist is to find specific nodes in the read
+    /// phase, and then get their mutable couterparts using `make_mut` in the
+    /// mutable state.
+    pub fn make_syntax_mut(&mut self, node: SyntaxNode) -> SyntaxNode {
+        self.mutated_tree.get_or_insert_with(|| TreeMutator::new(&node)).make_syntax_mut(&node)
+    }
+
+    /// Remove specified `range` of text.
+    pub fn delete(&mut self, range: TextRange) {
+        self.edit.delete(range)
+    }
+    /// Append specified `text` at the given `offset`
+    pub fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
+        self.edit.insert(offset, text.into())
+    }
+    /// Append specified `snippet` at the given `offset`
+    pub fn insert_snippet(
+        &mut self,
+        _cap: SnippetCap,
+        offset: TextSize,
+        snippet: impl Into<String>,
+    ) {
+        self.source_change.is_snippet = true;
+        self.insert(offset, snippet);
+    }
+    /// Replaces specified `range` of text with a given string.
+    pub fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
+        self.edit.replace(range, replace_with.into())
+    }
+    /// Replaces specified `range` of text with a given `snippet`.
+    pub fn replace_snippet(
+        &mut self,
+        _cap: SnippetCap,
+        range: TextRange,
+        snippet: impl Into<String>,
+    ) {
+        self.source_change.is_snippet = true;
+        self.replace(range, snippet);
+    }
+    pub fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
+        algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
+    }
+    pub fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
+        let file_system_edit = FileSystemEdit::CreateFile { dst, initial_contents: content.into() };
+        self.source_change.push_file_system_edit(file_system_edit);
+    }
+    pub fn move_file(&mut self, src: FileId, dst: AnchoredPathBuf) {
+        let file_system_edit = FileSystemEdit::MoveFile { src, dst };
+        self.source_change.push_file_system_edit(file_system_edit);
+    }
+    pub fn trigger_signature_help(&mut self) {
+        self.trigger_signature_help = true;
+    }
+
+    pub fn finish(mut self) -> SourceChange {
+        self.commit();
+        mem::take(&mut self.source_change)
+    }
+}
+
 #[derive(Debug, Clone)]
 pub enum FileSystemEdit {
     CreateFile { dst: AnchoredPathBuf, initial_contents: String },
diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs
index 97ea5c456a6..5694f33525e 100644
--- a/crates/ide-diagnostics/src/handlers/inactive_code.rs
+++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs
@@ -43,7 +43,7 @@ mod tests {
     use crate::{tests::check_diagnostics_with_config, DiagnosticsConfig};
 
     pub(crate) fn check(ra_fixture: &str) {
-        let config = DiagnosticsConfig::default();
+        let config = DiagnosticsConfig::test_sample();
         check_diagnostics_with_config(config, ra_fixture)
     }
 
diff --git a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
index aa7fcffb48a..a21db5b2cec 100644
--- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
+++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
@@ -1,21 +1,30 @@
 //! This diagnostic provides an assist for creating a struct definition from a JSON
 //! example.
 
-use ide_db::{base_db::FileId, source_change::SourceChange};
+use hir::{PathResolution, Semantics};
+use ide_db::{
+    base_db::FileId,
+    helpers::mod_path_to_ast,
+    imports::insert_use::{insert_use, ImportScope},
+    source_change::SourceChangeBuilder,
+    RootDatabase,
+};
 use itertools::Itertools;
-use stdx::format_to;
+use stdx::{format_to, never};
 use syntax::{
     ast::{self, make},
     SyntaxKind, SyntaxNode,
 };
 use text_edit::TextEdit;
 
-use crate::{fix, Diagnostic, Severity};
+use crate::{fix, Diagnostic, DiagnosticsConfig, Severity};
 
 #[derive(Default)]
 struct State {
     result: String,
     struct_counts: usize,
+    has_serialize: bool,
+    has_deserialize: bool,
 }
 
 impl State {
@@ -24,6 +33,25 @@ impl State {
         make::name(&format!("Struct{}", self.struct_counts))
     }
 
+    fn serde_derive(&self) -> String {
+        let mut v = vec![];
+        if self.has_serialize {
+            v.push("Serialize");
+        }
+        if self.has_deserialize {
+            v.push("Deserialize");
+        }
+        match v.as_slice() {
+            [] => "".to_string(),
+            [x] => format!("#[derive({x})]\n"),
+            [x, y] => format!("#[derive({x}, {y})]\n"),
+            _ => {
+                never!();
+                "".to_string()
+            }
+        }
+    }
+
     fn build_struct(&mut self, value: &serde_json::Map<String, serde_json::Value>) -> ast::Type {
         let name = self.generate_new_name();
         let ty = make::ty(&name.to_string());
@@ -36,7 +64,7 @@ impl State {
             ))
             .into(),
         );
-        format_to!(self.result, "#[derive(Serialize, Deserialize)]\n{}\n", strukt);
+        format_to!(self.result, "{}{}\n", self.serde_derive(), strukt);
         ty
     }
 
@@ -44,10 +72,10 @@ impl State {
         match value {
             serde_json::Value::Null => make::ty_unit(),
             serde_json::Value::Bool(_) => make::ty("bool"),
-            serde_json::Value::Number(x) => make::ty(if x.is_i64() { "i64" } else { "f64" }),
+            serde_json::Value::Number(it) => make::ty(if it.is_i64() { "i64" } else { "f64" }),
             serde_json::Value::String(_) => make::ty("String"),
-            serde_json::Value::Array(x) => {
-                let ty = match x.iter().next() {
+            serde_json::Value::Array(it) => {
+                let ty = match it.iter().next() {
                     Some(x) => self.type_of(x),
                     None => make::ty_placeholder(),
                 };
@@ -58,37 +86,91 @@ impl State {
     }
 }
 
-pub(crate) fn json_in_items(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
-    if node.kind() == SyntaxKind::ERROR
-        && node.first_token().map(|x| x.kind()) == Some(SyntaxKind::L_CURLY)
-        && node.last_token().map(|x| x.kind()) == Some(SyntaxKind::R_CURLY)
-    {
-        let node_string = node.to_string();
-        if let Ok(x) = serde_json::from_str(&node_string) {
-            if let serde_json::Value::Object(x) = x {
-                let range = node.text_range();
-                let mut edit = TextEdit::builder();
-                edit.delete(range);
-                let mut state = State::default();
-                state.build_struct(&x);
-                edit.insert(range.start(), state.result);
-                acc.push(
-                    Diagnostic::new(
-                        "json-is-not-rust",
-                        "JSON syntax is not valid as a Rust item",
-                        range,
-                    )
-                    .severity(Severity::WeakWarning)
-                    .with_fixes(Some(vec![fix(
-                        "convert_json_to_struct",
-                        "Convert JSON to struct",
-                        SourceChange::from_text_edit(file_id, edit.finish()),
-                        range,
-                    )])),
-                );
+pub(crate) fn json_in_items(
+    sema: &Semantics<'_, RootDatabase>,
+    acc: &mut Vec<Diagnostic>,
+    file_id: FileId,
+    node: &SyntaxNode,
+    config: &DiagnosticsConfig,
+) {
+    (|| {
+        if node.kind() == SyntaxKind::ERROR
+            && node.first_token().map(|x| x.kind()) == Some(SyntaxKind::L_CURLY)
+            && node.last_token().map(|x| x.kind()) == Some(SyntaxKind::R_CURLY)
+        {
+            let node_string = node.to_string();
+            if let Ok(it) = serde_json::from_str(&node_string) {
+                if let serde_json::Value::Object(it) = it {
+                    let import_scope = ImportScope::find_insert_use_container(node, sema)?;
+                    let range = node.text_range();
+                    let mut edit = TextEdit::builder();
+                    edit.delete(range);
+                    let mut state = State::default();
+                    let semantics_scope = sema.scope(node)?;
+                    let scope_resolve =
+                        |it| semantics_scope.speculative_resolve(&make::path_from_text(it));
+                    let scope_has = |it| scope_resolve(it).is_some();
+                    let deserialize_resolved = scope_resolve("::serde::Deserialize");
+                    let serialize_resolved = scope_resolve("::serde::Serialize");
+                    state.has_deserialize = deserialize_resolved.is_some();
+                    state.has_serialize = serialize_resolved.is_some();
+                    state.build_struct(&it);
+                    edit.insert(range.start(), state.result);
+                    acc.push(
+                        Diagnostic::new(
+                            "json-is-not-rust",
+                            "JSON syntax is not valid as a Rust item",
+                            range,
+                        )
+                        .severity(Severity::WeakWarning)
+                        .with_fixes(Some(vec![{
+                            let mut scb = SourceChangeBuilder::new(file_id);
+                            let scope = match import_scope.clone() {
+                                ImportScope::File(it) => ImportScope::File(scb.make_mut(it)),
+                                ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)),
+                                ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)),
+                            };
+                            let current_module = semantics_scope.module();
+                            if !scope_has("Serialize") {
+                                if let Some(PathResolution::Def(it)) = serialize_resolved {
+                                    if let Some(it) = current_module.find_use_path_prefixed(
+                                        sema.db,
+                                        it,
+                                        config.insert_use.prefix_kind,
+                                    ) {
+                                        insert_use(
+                                            &scope,
+                                            mod_path_to_ast(&it),
+                                            &config.insert_use,
+                                        );
+                                    }
+                                }
+                            }
+                            if !scope_has("Deserialize") {
+                                if let Some(PathResolution::Def(it)) = deserialize_resolved {
+                                    if let Some(it) = current_module.find_use_path_prefixed(
+                                        sema.db,
+                                        it,
+                                        config.insert_use.prefix_kind,
+                                    ) {
+                                        insert_use(
+                                            &scope,
+                                            mod_path_to_ast(&it),
+                                            &config.insert_use,
+                                        );
+                                    }
+                                }
+                            }
+                            let mut sc = scb.finish();
+                            sc.insert_source_edit(file_id, edit.finish());
+                            fix("convert_json_to_struct", "Convert JSON to struct", sc, range)
+                        }])),
+                    );
+                }
             }
         }
-    }
+        Some(())
+    })();
 }
 
 #[cfg(test)]
@@ -100,7 +182,7 @@ mod tests {
 
     #[test]
     fn diagnostic_for_simple_case() {
-        let mut config = DiagnosticsConfig::default();
+        let mut config = DiagnosticsConfig::test_sample();
         config.disabled.insert("syntax-error".to_string());
         check_diagnostics_with_config(
             config,
@@ -115,6 +197,13 @@ mod tests {
     fn types_of_primitives() {
         check_fix(
             r#"
+            //- /lib.rs crate:lib deps:serde
+            use serde::Serialize;
+
+            fn some_garbage() {
+
+            }
+
             {$0
                 "foo": "bar",
                 "bar": 2.3,
@@ -122,9 +211,20 @@ mod tests {
                 "bay": 57,
                 "box": true
             }
+            //- /serde.rs crate:serde
+
+            pub trait Serialize {
+                fn serialize() -> u8;
+            }
             "#,
             r#"
-            #[derive(Serialize, Deserialize)]
+            use serde::Serialize;
+
+            fn some_garbage() {
+
+            }
+
+            #[derive(Serialize)]
             struct Struct1{ bar: f64, bay: i64, baz: (), r#box: bool, foo: String }
 
             "#,
@@ -144,11 +244,8 @@ mod tests {
             }
             "#,
             r#"
-            #[derive(Serialize, Deserialize)]
             struct Struct3{  }
-            #[derive(Serialize, Deserialize)]
             struct Struct2{ kind: String, value: Struct3 }
-            #[derive(Serialize, Deserialize)]
             struct Struct1{ bar: Struct2, foo: String }
 
             "#,
@@ -159,6 +256,7 @@ mod tests {
     fn arrays() {
         check_fix(
             r#"
+            //- /lib.rs crate:lib deps:serde
             {
                 "of_string": ["foo", "2", "x"], $0
                 "of_object": [{
@@ -171,8 +269,19 @@ mod tests {
                 "nested": [[[2]]],
                 "empty": []
             }
+            //- /serde.rs crate:serde
+
+            pub trait Serialize {
+                fn serialize() -> u8;
+            }
+            pub trait Deserialize {
+                fn deserialize() -> u8;
+            }
             "#,
             r#"
+            use serde::Serialize;
+            use serde::Deserialize;
+
             #[derive(Serialize, Deserialize)]
             struct Struct2{ x: i64, y: i64 }
             #[derive(Serialize, Deserialize)]
diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs
index d6a66dc1509..43ff4ed5a6c 100644
--- a/crates/ide-diagnostics/src/handlers/macro_error.rs
+++ b/crates/ide-diagnostics/src/handlers/macro_error.rs
@@ -79,7 +79,7 @@ pub macro panic {
 
     #[test]
     fn include_macro_should_allow_empty_content() {
-        let mut config = DiagnosticsConfig::default();
+        let mut config = DiagnosticsConfig::test_sample();
 
         // FIXME: This is a false-positive, the file is actually linked in via
         // `include!` macro
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 7034f010e1b..61e63ea7a93 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -60,6 +60,7 @@ use hir::{diagnostics::AnyDiagnostic, InFile, Semantics};
 use ide_db::{
     assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
     base_db::{FileId, FileRange, SourceDatabase},
+    imports::insert_use::InsertUseConfig,
     label::Label,
     source_change::SourceChange,
     FxHashSet, RootDatabase,
@@ -140,13 +141,37 @@ impl Default for ExprFillDefaultMode {
     }
 }
 
-#[derive(Default, Debug, Clone)]
+#[derive(Debug, Clone)]
 pub struct DiagnosticsConfig {
     pub proc_macros_enabled: bool,
     pub proc_attr_macros_enabled: bool,
     pub disable_experimental: bool,
     pub disabled: FxHashSet<String>,
     pub expr_fill_default: ExprFillDefaultMode,
+    // FIXME: We may want to include a whole `AssistConfig` here
+    pub insert_use: InsertUseConfig,
+}
+
+impl DiagnosticsConfig {
+    pub fn test_sample() -> Self {
+        use hir::PrefixKind;
+        use ide_db::imports::insert_use::ImportGranularity;
+
+        Self {
+            proc_macros_enabled: Default::default(),
+            proc_attr_macros_enabled: Default::default(),
+            disable_experimental: Default::default(),
+            disabled: Default::default(),
+            expr_fill_default: Default::default(),
+            insert_use: InsertUseConfig {
+                granularity: ImportGranularity::Preserve,
+                enforce_granularity: false,
+                prefix_kind: PrefixKind::Plain,
+                group: false,
+                skip_glob_imports: false,
+            },
+        }
+    }
 }
 
 struct DiagnosticsContext<'a> {
@@ -173,10 +198,12 @@ pub fn diagnostics(
         }),
     );
 
-    for node in parse.tree().syntax().descendants() {
+    let parse = sema.parse(file_id);
+
+    for node in parse.syntax().descendants() {
         handlers::useless_braces::useless_braces(&mut res, file_id, &node);
         handlers::field_shorthand::field_shorthand(&mut res, file_id, &node);
-        handlers::json_is_not_rust::json_in_items(&mut res, file_id, &node);
+        handlers::json_is_not_rust::json_in_items(&sema, &mut res, file_id, &node, &config);
     }
 
     let module = sema.to_module_def(file_id);
diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs
index 7312bca32fe..729619cfde0 100644
--- a/crates/ide-diagnostics/src/tests.rs
+++ b/crates/ide-diagnostics/src/tests.rs
@@ -37,7 +37,7 @@ fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
     let after = trim_indent(ra_fixture_after);
 
     let (db, file_position) = RootDatabase::with_position(ra_fixture_before);
-    let mut conf = DiagnosticsConfig::default();
+    let mut conf = DiagnosticsConfig::test_sample();
     conf.expr_fill_default = ExprFillDefaultMode::Default;
     let diagnostic =
         super::diagnostics(&db, &conf, &AssistResolveStrategy::All, file_position.file_id)
@@ -69,7 +69,7 @@ pub(crate) fn check_no_fix(ra_fixture: &str) {
     let (db, file_position) = RootDatabase::with_position(ra_fixture);
     let diagnostic = super::diagnostics(
         &db,
-        &DiagnosticsConfig::default(),
+        &DiagnosticsConfig::test_sample(),
         &AssistResolveStrategy::All,
         file_position.file_id,
     )
@@ -82,7 +82,7 @@ pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
     let (db, file_id) = RootDatabase::with_single_file(ra_fixture);
     let diagnostics = super::diagnostics(
         &db,
-        &DiagnosticsConfig::default(),
+        &DiagnosticsConfig::test_sample(),
         &AssistResolveStrategy::All,
         file_id,
     );
@@ -91,7 +91,7 @@ pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
 
 #[track_caller]
 pub(crate) fn check_diagnostics(ra_fixture: &str) {
-    let mut config = DiagnosticsConfig::default();
+    let mut config = DiagnosticsConfig::test_sample();
     config.disabled.insert("inactive-code".to_string());
     check_diagnostics_with_config(config, ra_fixture)
 }
@@ -127,7 +127,7 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
 
 #[test]
 fn test_disabled_diagnostics() {
-    let mut config = DiagnosticsConfig::default();
+    let mut config = DiagnosticsConfig::test_sample();
     config.disabled.insert("unresolved-module".into());
 
     let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
@@ -137,7 +137,7 @@ fn test_disabled_diagnostics() {
 
     let diagnostics = super::diagnostics(
         &db,
-        &DiagnosticsConfig::default(),
+        &DiagnosticsConfig::test_sample(),
         &AssistResolveStrategy::All,
         file_id,
     );
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 52511ceb580..247007db0a7 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -43,7 +43,7 @@ impl flags::Diagnostics {
                 println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
                 for diagnostic in analysis
                     .diagnostics(
-                        &DiagnosticsConfig::default(),
+                        &DiagnosticsConfig::test_sample(),
                         AssistResolveStrategy::None,
                         file_id,
                     )
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index ac0fdf85a77..1629c1dd328 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -881,6 +881,7 @@ impl Config {
                 ExprFillDefaultDef::Todo => ExprFillDefaultMode::Todo,
                 ExprFillDefaultDef::Default => ExprFillDefaultMode::Default,
             },
+            insert_use: self.insert_use_config(),
         }
     }