Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/auto_import.rs18
-rw-r--r--crates/ide-assists/src/handlers/convert_bool_to_enum.rs19
-rw-r--r--crates/ide-completion/src/lib.rs17
-rw-r--r--crates/ide-db/src/imports/insert_use.rs308
-rw-r--r--crates/ide-db/src/imports/insert_use/tests.rs11
-rw-r--r--crates/ide-diagnostics/src/handlers/json_is_not_rust.rs22
-rw-r--r--crates/syntax/src/ast/syntax_factory/constructors.rs6
7 files changed, 144 insertions, 257 deletions
diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs
index 9bfb47e69d..ac0bae7cd9 100644
--- a/crates/ide-assists/src/handlers/auto_import.rs
+++ b/crates/ide-assists/src/handlers/auto_import.rs
@@ -7,7 +7,7 @@ use ide_db::{
helpers::mod_path_to_ast,
imports::{
import_assets::{ImportAssets, ImportCandidate, LocatedImport, TraitImportCandidate},
- insert_use::{ImportScope, insert_use, insert_use_as_alias},
+ insert_use::{ImportScope, insert_use_as_alias_with_editor, insert_use_with_editor},
},
};
use syntax::{AstNode, Edition, SyntaxNode, ast, match_ast};
@@ -125,8 +125,14 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Opt
(AssistId::quick_fix("auto_import"), import_path.display(ctx.db(), edition));
let add_normal_import = |acc: &mut Assists, label| {
acc.add_group(&group_label, assist_id, label, range, |builder| {
- let scope = builder.make_import_scope_mut(scope.clone());
- insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use);
+ let editor = builder.make_editor(scope.as_syntax_node());
+ insert_use_with_editor(
+ &scope,
+ mod_path_to_ast(&import_path, edition),
+ &ctx.config.insert_use,
+ &editor,
+ );
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
})
};
let add_underscore_import = |acc: &mut Assists, name: &TraitImportCandidate<'_>, label| {
@@ -139,13 +145,15 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Opt
name.assoc_item_name.text()
));
acc.add_group(&group_label, assist_id, label, range, |builder| {
- let scope = builder.make_import_scope_mut(scope.clone());
- insert_use_as_alias(
+ let editor = builder.make_editor(scope.as_syntax_node());
+ insert_use_as_alias_with_editor(
&scope,
mod_path_to_ast(&import_path, edition),
&ctx.config.insert_use,
edition,
+ &editor,
);
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
});
};
diff --git a/crates/ide-assists/src/handlers/convert_bool_to_enum.rs b/crates/ide-assists/src/handlers/convert_bool_to_enum.rs
index 456a4d1fcf..4c3168219f 100644
--- a/crates/ide-assists/src/handlers/convert_bool_to_enum.rs
+++ b/crates/ide-assists/src/handlers/convert_bool_to_enum.rs
@@ -1,12 +1,13 @@
use either::Either;
use hir::ModuleDef;
+use ide_db::imports::insert_use::insert_use_with_editor;
use ide_db::text_edit::TextRange;
use ide_db::{
- FxHashSet,
+ FileId, FxHashSet,
assists::AssistId,
defs::Definition,
helpers::mod_path_to_ast_with_factory,
- imports::insert_use::{ImportScope, insert_use},
+ imports::insert_use::ImportScope,
search::{FileReference, UsageSearchResult},
source_change::SourceChangeBuilder,
};
@@ -85,8 +86,10 @@ pub(crate) fn convert_bool_to_enum(acc: &mut Assists, ctx: &AssistContext<'_, '_
&mut delayed_mutations,
&make,
);
- for (scope, path) in delayed_mutations {
- insert_use(&scope, path, &ctx.config.insert_use);
+ for (file_id, scope, path) in delayed_mutations {
+ let editor = edit.make_editor(scope.as_syntax_node());
+ insert_use_with_editor(&scope, path, &ctx.config.insert_use, &editor);
+ edit.add_file_edits(file_id, editor);
}
},
)
@@ -212,11 +215,12 @@ fn replace_usages(
usages: UsageSearchResult,
target_definition: Definition,
target_module: &hir::Module,
- delayed_mutations: &mut Vec<(ImportScope, ast::Path)>,
+ delayed_mutations: &mut Vec<(FileId, ImportScope, ast::Path)>,
make: &SyntaxFactory,
) {
for (file_id, references) in usages {
- edit.edit_file(file_id.file_id(ctx.db()));
+ let vfs_file_id = file_id.file_id(ctx.db());
+ edit.edit_file(vfs_file_id);
let refs_with_imports =
augment_references_with_imports(ctx, references, target_module, make);
@@ -323,8 +327,7 @@ fn replace_usages(
// add imports across modules where needed
if let Some((scope, path)) = import_data {
- let scope = edit.make_import_scope_mut(scope);
- delayed_mutations.push((scope, path));
+ delayed_mutations.push((vfs_file_id, scope, path));
}
},
)
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index 66ecb790a0..4ca3257c5c 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -23,7 +23,7 @@ use ide_db::{
syntax_helpers::tree_diff::diff,
text_edit::TextEdit,
};
-use syntax::ast::make;
+use syntax::{AstNode, syntax_editor::SyntaxEditor};
use crate::{
completions::Completions,
@@ -296,23 +296,26 @@ pub fn resolve_completion_edits(
let current_module = sema.scope(position_for_import)?.module();
let current_crate = current_module.krate(db);
let current_edition = current_crate.edition(db);
- let new_ast = scope.clone_for_update();
let mut import_insert = TextEdit::builder();
+ let (editor, _) = SyntaxEditor::new(original_file.syntax().clone());
+ let make = editor.make();
imports.into_iter().for_each(|import| {
- let full_path = make::path_from_text_with_edition(&import.path, current_edition);
+ let full_path = make.path_from_text_with_edition(&import.path, current_edition);
if import.as_underscore {
- insert_use::insert_use_as_alias(
- &new_ast,
+ insert_use::insert_use_as_alias_with_editor(
+ &scope,
full_path,
&config.insert_use,
current_edition,
+ &editor,
);
} else {
- insert_use::insert_use(&new_ast, full_path, &config.insert_use);
+ insert_use::insert_use_with_editor(&scope, full_path, &config.insert_use, &editor);
}
});
- diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
+ let edit = editor.finish();
+ diff(edit.old_root(), edit.new_root()).into_text_edit(&mut import_insert);
Some(vec![import_insert.finish()])
}
diff --git a/crates/ide-db/src/imports/insert_use.rs b/crates/ide-db/src/imports/insert_use.rs
index 7402ec8f5a..4b0373271c 100644
--- a/crates/ide-db/src/imports/insert_use.rs
+++ b/crates/ide-db/src/imports/insert_use.rs
@@ -6,10 +6,11 @@ use std::cmp::Ordering;
use hir::Semantics;
use syntax::{
- Direction, NodeOrToken, SyntaxKind, SyntaxNode, algo,
- ast::{self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind, make},
+ NodeOrToken, SyntaxKind, SyntaxNode,
+ ast::{
+ self, AstNode, HasAttrs, HasModuleItem, HasVisibility, PathSegmentKind, edit::IndentLevel,
+ },
syntax_editor::{Position, SyntaxEditor},
- ted,
};
use crate::{
@@ -147,24 +148,6 @@ impl ImportScope {
ImportScopeKind::Block(block) => block.syntax(),
}
}
-
- pub fn clone_for_update(&self) -> Self {
- Self {
- kind: match &self.kind {
- ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()),
- ImportScopeKind::Module(item_list) => {
- ImportScopeKind::Module(item_list.clone_for_update())
- }
- ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()),
- },
- required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(),
- }
- }
-}
-
-/// 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(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
- insert_use_with_alias_option(scope, path, cfg, None);
}
/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
@@ -177,11 +160,43 @@ pub fn insert_use_with_editor(
insert_use_with_alias_option_with_editor(scope, path, cfg, None, syntax_editor);
}
-pub fn insert_use_as_alias(
+pub fn insert_uses_with_editor(
+ scope: &ImportScope,
+ paths: impl IntoIterator<Item = ast::Path>,
+ cfg: &InsertUseConfig,
+ syntax_editor: &SyntaxEditor,
+) {
+ let paths = paths.into_iter().collect::<Vec<_>>();
+ if paths.len() > 1
+ && scope.as_syntax_node().parent().is_none()
+ && scope.required_cfgs.is_empty()
+ && !scope.as_syntax_node().children().any(|node| ast::Use::cast(node).is_some())
+ {
+ let make = syntax_editor.make();
+ let elements = paths
+ .into_iter()
+ .flat_map(|path| {
+ let use_tree = make.use_tree(path, None, None, false);
+ let use_item = make.use_(None, None, use_tree);
+ [use_item.syntax().clone().into(), make.whitespace("\n").into()]
+ })
+ .chain([make.whitespace("\n").into()])
+ .collect();
+ syntax_editor.insert_all(Position::first_child_of(scope.as_syntax_node()), elements);
+ return;
+ }
+
+ for path in paths {
+ insert_use_with_editor(scope, path, cfg, syntax_editor);
+ }
+}
+
+pub fn insert_use_as_alias_with_editor(
scope: &ImportScope,
path: ast::Path,
cfg: &InsertUseConfig,
edition: span::Edition,
+ editor: &SyntaxEditor,
) {
let text: &str = "use foo as _";
let parse = syntax::SourceFile::parse(text, edition);
@@ -193,71 +208,7 @@ pub fn insert_use_as_alias(
.expect("Failed to make ast node `Rename`");
let alias = node.rename();
- insert_use_with_alias_option(scope, path, cfg, alias);
-}
-
-fn insert_use_with_alias_option(
- scope: &ImportScope,
- path: ast::Path,
- cfg: &InsertUseConfig,
- alias: Option<ast::Rename>,
-) {
- let _p = tracing::info_span!("insert_use_with_alias_option").entered();
- let mut mb = match cfg.granularity {
- ImportGranularity::Crate => Some(MergeBehavior::Crate),
- ImportGranularity::Module => Some(MergeBehavior::Module),
- ImportGranularity::One => Some(MergeBehavior::One),
- ImportGranularity::Item => None,
- };
- if !cfg.enforce_granularity {
- let file_granularity = guess_granularity_from_scope(scope);
- mb = match file_granularity {
- ImportGranularityGuess::Unknown => mb,
- ImportGranularityGuess::Item => None,
- ImportGranularityGuess::Module => Some(MergeBehavior::Module),
- // We use the user's setting to infer if this is module or item.
- ImportGranularityGuess::ModuleOrItem => match mb {
- Some(MergeBehavior::Module) | None => mb,
- // There isn't really a way to decide between module or item here, so we just pick one.
- // FIXME: Maybe it is possible to infer based on semantic analysis?
- Some(MergeBehavior::One | MergeBehavior::Crate) => Some(MergeBehavior::Module),
- },
- ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
- ImportGranularityGuess::CrateOrModule => match mb {
- Some(MergeBehavior::Crate | MergeBehavior::Module) => mb,
- Some(MergeBehavior::One) | None => Some(MergeBehavior::Crate),
- },
- ImportGranularityGuess::One => Some(MergeBehavior::One),
- };
- }
-
- let mut use_tree = make::use_tree(path, None, alias, false);
- if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
- use_tree = use_tree.clone_for_update();
- use_tree.wrap_in_tree_list();
- }
- let use_item = make::use_(None, None, use_tree).clone_for_update();
- for attr in
- scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update())
- {
- ted::insert(ted::Position::first_child_of(use_item.syntax()), attr);
- }
-
- // merge into existing imports if possible
- if let Some(mb) = mb {
- let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
- for existing_use in
- scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
- {
- if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
- ted::replace(existing_use.syntax(), merged.syntax());
- return;
- }
- }
- }
- // 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, use_item, cfg.group);
+ insert_use_with_alias_option_with_editor(scope, path, cfg, alias, editor);
}
fn insert_use_with_alias_option_with_editor(
@@ -301,10 +252,7 @@ fn insert_use_with_alias_option_with_editor(
if mb == Some(MergeBehavior::One) && use_tree.path().is_some() {
use_tree.wrap_in_tree_list();
}
- let use_item = make::use_(None, None, use_tree);
- for attr in scope.required_cfgs.iter().map(|attr| attr.syntax().clone()) {
- syntax_editor.insert(Position::first_child_of(use_item.syntax()), attr);
- }
+ let use_item = make.use_(scope.required_cfgs.iter().cloned().rev(), None, use_tree);
// merge into existing imports if possible
if let Some(mb) = mb {
@@ -469,123 +417,6 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess {
}
}
-fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) {
- let scope_syntax = scope.as_syntax_node();
- let insert_use_tree =
- use_item.use_tree().expect("`use_item` should have a use tree for `insert_path`");
- let group = ImportGroup::new(&insert_use_tree);
- let path_node_iter = scope_syntax
- .children()
- .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
- .flat_map(|(use_, node)| {
- let tree = use_.use_tree()?;
- Some((tree, node))
- });
-
- if group_imports {
- // Iterator that discards anything that's not in the required grouping
- // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
- let group_iter = path_node_iter
- .clone()
- .skip_while(|(use_tree, ..)| ImportGroup::new(use_tree) != group)
- .take_while(|(use_tree, ..)| ImportGroup::new(use_tree) == group);
-
- // 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(|(use_tree, _)| use_tree_cmp(&insert_use_tree, use_tree) != Ordering::Greater);
-
- if let Some((.., node)) = post_insert {
- cov_mark::hit!(insert_group);
- // 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);
- // 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(|(use_tree, ..)| ImportGroup::new(use_tree) > 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;
- }
- } else {
- // There exists a group, so append to the end of it
- if let Some((_, node)) = path_node_iter.last() {
- cov_mark::hit!(insert_no_grouping_last);
- ted::insert(ted::Position::after(node), use_item.syntax());
- return;
- }
- }
-
- let l_curly = match &scope.kind {
- ImportScopeKind::File(_) => None,
- // don't insert the imports before the item list/block expr's opening curly brace
- ImportScopeKind::Module(item_list) => item_list.l_curly_token(),
- // don't insert the imports before the item list's opening curly brace
- ImportScopeKind::Block(block) => block.l_curly_token(),
- };
- // there are no imports in this file at all
- // so put the import after all inner module attributes and possible license header comments
- if let Some(last_inner_element) = scope_syntax
- .children_with_tokens()
- // skip the curly brace
- .skip(l_curly.is_some() as usize)
- .take_while(|child| match child {
- NodeOrToken::Node(node) => {
- is_inner_attribute(node.clone()) && ast::Item::cast(node.clone()).is_none()
- }
- NodeOrToken::Token(token) => {
- [SyntaxKind::WHITESPACE, SyntaxKind::COMMENT, SyntaxKind::SHEBANG]
- .contains(&token.kind())
- }
- })
- .filter(|child| child.as_token().is_none_or(|t| t.kind() != SyntaxKind::WHITESPACE))
- .last()
- {
- cov_mark::hit!(insert_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());
- } else {
- match l_curly {
- Some(b) => {
- cov_mark::hit!(insert_empty_module);
- ted::insert(ted::Position::after(&b), make::tokens::single_newline());
- ted::insert(ted::Position::after(&b), use_item.syntax());
- }
- None => {
- cov_mark::hit!(insert_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());
- }
- }
- }
-}
-
fn insert_use_with_editor_(
scope: &ImportScope,
use_item: ast::Use,
@@ -623,12 +454,18 @@ fn insert_use_with_editor_(
if let Some((.., node)) = post_insert {
cov_mark::hit!(insert_group);
// insert our import before that element
- return syntax_editor.insert(Position::before(node), use_item.syntax());
+ return syntax_editor.insert_all(
+ Position::before(node),
+ vec![use_item.syntax().clone().into(), make.whitespace("\n").into()],
+ );
}
if let Some(node) = last {
cov_mark::hit!(insert_group_last);
// there is no element after our new import, so append it to the end of the group
- return syntax_editor.insert(Position::after(node), use_item.syntax());
+ return syntax_editor.insert_all(
+ Position::after(node),
+ vec![make.whitespace("\n").into(), use_item.syntax().clone().into()],
+ );
}
// the group we were looking for actually doesn't exist, so insert
@@ -640,24 +477,29 @@ fn insert_use_with_editor_(
.find(|(use_tree, ..)| ImportGroup::new(use_tree) > group);
if let Some((.., node)) = post_group {
cov_mark::hit!(insert_group_new_group);
- syntax_editor.insert(Position::before(&node), use_item.syntax());
- if let Some(node) = algo::non_trivia_sibling(node.into(), Direction::Prev) {
- syntax_editor.insert(Position::after(node), make.whitespace("\n"));
- }
+ syntax_editor.insert_all(
+ Position::before(&node),
+ vec![use_item.syntax().clone().into(), make.whitespace("\n\n").into()],
+ );
return;
}
// there is no such group, so append after the last one
if let Some(node) = last {
cov_mark::hit!(insert_group_no_group);
- syntax_editor.insert(Position::after(&node), use_item.syntax());
- syntax_editor.insert(Position::after(node), make.whitespace("\n"));
+ syntax_editor.insert_all(
+ Position::after(&node),
+ vec![make.whitespace("\n\n").into(), use_item.syntax().clone().into()],
+ );
return;
}
} else {
// There exists a group, so append to the end of it
if let Some((_, node)) = path_node_iter.last() {
cov_mark::hit!(insert_no_grouping_last);
- syntax_editor.insert(Position::after(node), use_item.syntax());
+ syntax_editor.insert_all(
+ Position::after(node),
+ vec![make.whitespace("\n").into(), use_item.syntax().clone().into()],
+ );
return;
}
}
@@ -688,20 +530,38 @@ fn insert_use_with_editor_(
.last()
{
cov_mark::hit!(insert_empty_inner_attr);
- syntax_editor.insert(Position::after(&last_inner_element), use_item.syntax());
- syntax_editor.insert(Position::after(last_inner_element), make.whitespace("\n"));
+ let indent = if l_curly.is_some() {
+ IndentLevel::from_node(scope_syntax) + 1
+ } else {
+ IndentLevel::zero()
+ };
+ syntax_editor.insert_all(
+ Position::after(&last_inner_element),
+ vec![
+ make.whitespace(&format!("\n\n{indent}")).into(),
+ use_item.syntax().clone().into(),
+ ],
+ );
} else {
match l_curly {
Some(b) => {
cov_mark::hit!(insert_empty_module);
- syntax_editor.insert(Position::after(&b), make.whitespace("\n"));
- syntax_editor.insert_with_whitespace(Position::after(&b), use_item.syntax());
+ let indent = IndentLevel::from_node(scope_syntax) + 1;
+ syntax_editor.insert_all(
+ Position::after(&b),
+ vec![
+ make.whitespace(&format!("\n{indent}")).into(),
+ use_item.syntax().clone().into(),
+ make.whitespace("\n").into(),
+ ],
+ );
}
None => {
cov_mark::hit!(insert_empty_file);
- syntax_editor
- .insert(Position::first_child_of(scope_syntax), make.whitespace("\n\n"));
- syntax_editor.insert(Position::first_child_of(scope_syntax), use_item.syntax());
+ syntax_editor.insert_all(
+ Position::first_child_of(scope_syntax),
+ vec![use_item.syntax().clone().into(), make.whitespace("\n\n").into()],
+ );
}
}
}
diff --git a/crates/ide-db/src/imports/insert_use/tests.rs b/crates/ide-db/src/imports/insert_use/tests.rs
index 6c7b97458d..4fa05c4603 100644
--- a/crates/ide-db/src/imports/insert_use/tests.rs
+++ b/crates/ide-db/src/imports/insert_use/tests.rs
@@ -1342,14 +1342,14 @@ fn check_with_config(
};
let sema = &Semantics::new(&db);
let source_file = sema.parse(file_id);
+ let (editor, _) = SyntaxEditor::new(source_file.syntax().clone());
let file = pos
.and_then(|pos| source_file.syntax().token_at_offset(pos.expect_offset()).next()?.parent())
.and_then(|it| ImportScope::find_insert_use_container(&it, sema))
.unwrap_or_else(|| ImportScope {
- kind: ImportScopeKind::File(source_file),
+ kind: ImportScopeKind::File(source_file.clone()),
required_cfgs: vec![],
- })
- .clone_for_update();
+ });
let path = ast::SourceFile::parse(&format!("use {path};"), span::Edition::CURRENT)
.tree()
.syntax()
@@ -1357,8 +1357,9 @@ fn check_with_config(
.find_map(ast::Path::cast)
.unwrap();
- insert_use(&file, path, config);
- let result = file.as_syntax_node().ancestors().last().unwrap().to_string();
+ insert_use_with_editor(&file, path, config, &editor);
+ let edit = editor.finish();
+ let result = edit.new_root().to_string();
assert_eq_text!(&trim_indent(ra_fixture_after), &result);
}
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 20bfcc2dee..24f1e3ad83 100644
--- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
+++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
@@ -2,12 +2,11 @@
//! example.
use hir::{FindPathConfig, PathResolution, Semantics};
+use ide_db::imports::insert_use::insert_uses_with_editor;
use ide_db::text_edit::TextEdit;
use ide_db::{
- EditionedFileId, FileRange, FxHashMap, RootDatabase,
- helpers::mod_path_to_ast,
- imports::insert_use::{ImportScope, insert_use},
- source_change::SourceChangeBuilder,
+ EditionedFileId, FileRange, FxHashMap, RootDatabase, helpers::mod_path_to_ast,
+ imports::insert_use::ImportScope, source_change::SourceChangeBuilder,
};
use itertools::Itertools;
use stdx::{format_to, never};
@@ -138,7 +137,7 @@ pub(crate) fn json_in_items(
.stable()
.with_fixes(Some(vec![{
let mut scb = SourceChangeBuilder::new(vfs_file_id);
- let scope = scb.make_import_scope_mut(import_scope);
+ let editor = scb.make_editor(import_scope.as_syntax_node());
let current_module = semantics_scope.module();
let cfg = FindPathConfig {
@@ -148,6 +147,7 @@ pub(crate) fn json_in_items(
allow_unstable: true,
};
+ let mut imports_to_insert = Vec::new();
if !scope_has("Serialize")
&& let Some(PathResolution::Def(it)) = serialize_resolved
&& let Some(it) = current_module.find_use_path(
@@ -157,7 +157,7 @@ pub(crate) fn json_in_items(
cfg,
)
{
- insert_use(&scope, mod_path_to_ast(&it, edition), &config.insert_use);
+ imports_to_insert.push(mod_path_to_ast(&it, edition));
}
if !scope_has("Deserialize")
&& let Some(PathResolution::Def(it)) = deserialize_resolved
@@ -168,8 +168,16 @@ pub(crate) fn json_in_items(
cfg,
)
{
- insert_use(&scope, mod_path_to_ast(&it, edition), &config.insert_use);
+ imports_to_insert.push(mod_path_to_ast(&it, edition));
}
+
+ insert_uses_with_editor(
+ &import_scope,
+ imports_to_insert,
+ &config.insert_use,
+ &editor,
+ );
+ scb.add_file_edits(vfs_file_id, editor);
let mut sc = scb.finish();
sc.insert_source_edit(vfs_file_id, edit.finish());
fix("convert_json_to_struct", "Convert JSON to struct", sc, range)
diff --git a/crates/syntax/src/ast/syntax_factory/constructors.rs b/crates/syntax/src/ast/syntax_factory/constructors.rs
index 5a01580c56..bebf595f00 100644
--- a/crates/syntax/src/ast/syntax_factory/constructors.rs
+++ b/crates/syntax/src/ast/syntax_factory/constructors.rs
@@ -2,7 +2,7 @@
use either::Either;
use crate::{
- AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
+ AstNode, Edition, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
ast::{
self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasName,
HasTypeBounds, HasVisibility, Lifetime, Param, RangeItem, make,
@@ -131,6 +131,10 @@ impl SyntaxFactory {
make::path_from_text(text).clone_for_update()
}
+ pub fn path_from_text_with_edition(&self, text: &str, edition: Edition) -> ast::Path {
+ make::path_from_text_with_edition(text, edition).clone_for_update()
+ }
+
pub fn path_concat(&self, first: ast::Path, second: ast::Path) -> ast::Path {
make::path_concat(first, second).clone_for_update()
}