Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs')
-rw-r--r--crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs176
1 files changed, 148 insertions, 28 deletions
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 f6526cdba4..009fc077ce 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,4 +1,4 @@
-use hir::AsAssocItem;
+use hir::{AsAssocItem, ModuleDef, PathResolution};
use ide_db::{
helpers::mod_path_to_ast,
imports::insert_use::{ImportScope, insert_use},
@@ -6,7 +6,8 @@ use ide_db::{
use syntax::{
AstNode, Edition, SyntaxNode,
ast::{self, HasGenericArgs, make},
- match_ast, ted,
+ match_ast,
+ syntax_editor::SyntaxEditor,
};
use crate::{AssistContext, AssistId, Assists};
@@ -30,26 +31,19 @@ pub(crate) fn replace_qualified_name_with_use(
acc: &mut Assists,
ctx: &AssistContext<'_>,
) -> Option<()> {
- let mut original_path: ast::Path = ctx.find_node_at_offset()?;
+ let original_path: ast::Path = ctx.find_node_at_offset()?;
// We don't want to mess with use statements
if original_path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
cov_mark::hit!(not_applicable_in_use);
return None;
}
- if original_path.qualifier().is_none() {
- original_path = original_path.parent_path()?;
- }
+ let original_path = target_path(ctx, original_path)?;
- // only offer replacement for non assoc items
- match ctx.sema.resolve_path(&original_path)? {
- hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (),
- _ => return None,
- }
// then search for an import for the first path segment of what we want to replace
// that way it is less likely that we import the item from a different location due re-exports
let module = match ctx.sema.resolve_path(&original_path.first_qualifier_or_self())? {
- hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module,
+ PathResolution::Def(module @ ModuleDef::Module(_)) => module,
_ => return None,
};
@@ -64,7 +58,7 @@ pub(crate) fn replace_qualified_name_with_use(
let path_to_qualifier = starts_with_name_ref
.then(|| {
let mod_ = ctx.sema.scope(original_path.syntax())?.module();
- let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(mod_.krate()));
+ let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(mod_.krate(ctx.sema.db)));
mod_.find_use_path(ctx.sema.db, module, ctx.config.insert_use.prefix_kind, cfg)
})
.flatten();
@@ -78,8 +72,10 @@ 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 scope = builder.make_import_scope_mut(scope);
- shorten_paths(scope.as_syntax_node(), &original_path);
+ let scope_node = scope.as_syntax_node();
+ let mut editor = builder.make_editor(scope_node);
+ shorten_paths(&mut editor, scope_node, &original_path);
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
let path = drop_generic_args(&original_path);
let edition = ctx
.sema
@@ -92,23 +88,42 @@ pub(crate) fn replace_qualified_name_with_use(
Some(qualifier) => make::path_concat(qualifier, path),
None => path,
};
+ let scope = builder.make_import_scope_mut(scope);
insert_use(&scope, path, &ctx.config.insert_use);
},
)
}
+fn target_path(ctx: &AssistContext<'_>, mut original_path: ast::Path) -> Option<ast::Path> {
+ let on_first = original_path.qualifier().is_none();
+
+ if on_first {
+ original_path = original_path.top_path();
+ }
+
+ match ctx.sema.resolve_path(&original_path)? {
+ PathResolution::Def(ModuleDef::Variant(_)) if on_first => original_path.qualifier(),
+ PathResolution::Def(def) if def.as_assoc_item(ctx.db()).is_some() => {
+ on_first.then_some(original_path.qualifier()?)
+ }
+ _ => Some(original_path),
+ }
+}
+
fn drop_generic_args(path: &ast::Path) -> ast::Path {
- let path = path.clone_for_update();
+ let path = path.clone_subtree();
+ let mut editor = SyntaxEditor::new(path.syntax().clone());
if let Some(segment) = path.segment()
&& let Some(generic_args) = segment.generic_arg_list()
{
- ted::remove(generic_args.syntax());
+ editor.delete(generic_args.syntax());
}
- path
+
+ ast::Path::cast(editor.finish().new_root().clone()).unwrap()
}
/// Mutates `node` to shorten `path` in all descendants of `node`.
-fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
+fn shorten_paths(editor: &mut SyntaxEditor, node: &SyntaxNode, path: &ast::Path) {
for child in node.children() {
match_ast! {
match child {
@@ -118,26 +133,26 @@ fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
// Don't descend into submodules, they don't have the same `use` items in scope.
// FIXME: This isn't true due to `super::*` imports?
ast::Module(_) => continue,
- ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
- shorten_paths(p.syntax(), path);
+ ast::Path(p) => if maybe_replace_path(editor, p.clone(), path.clone()).is_none() {
+ shorten_paths(editor, p.syntax(), path);
},
- _ => shorten_paths(&child, path),
+ _ => shorten_paths(editor, &child, path),
}
}
}
}
-fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
+fn maybe_replace_path(editor: &mut SyntaxEditor, path: ast::Path, target: ast::Path) -> Option<()> {
if !path_eq_no_generics(path.clone(), target) {
return None;
}
// Shorten `path`, leaving only its last segment.
if let Some(parent) = path.qualifier() {
- ted::remove(parent.syntax());
+ editor.delete(parent.syntax());
}
if let Some(double_colon) = path.coloncolon_token() {
- ted::remove(&double_colon);
+ editor.delete(double_colon);
}
Some(())
@@ -270,12 +285,117 @@ fn main() {
}
",
r"
-use std::fmt;
+use std::fmt::Debug;
mod std { pub mod fmt { pub trait Debug {} } }
fn main() {
- fmt::Debug;
- let x: fmt::Debug = fmt::Debug;
+ Debug;
+ let x: Debug = Debug;
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn assist_runs_on_first_segment_for_enum() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ $0std::option::Option;
+ let x: std::option::Option<()> = std::option::Option::Some(());
+}
+ ",
+ r"
+use std::option::Option;
+
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ Option;
+ let x: Option<()> = Option::Some(());
+}
+ ",
+ );
+
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ std::option::Option;
+ let x: std::option::Option<()> = $0std::option::Option::Some(());
+}
+ ",
+ r"
+use std::option::Option;
+
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ Option;
+ let x: Option<()> = Option::Some(());
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn assist_runs_on_first_segment_for_assoc_type() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod foo { pub struct Foo; impl Foo { pub fn foo() {} } }
+fn main() {
+ $0foo::Foo::foo();
+}
+ ",
+ r"
+use foo::Foo;
+
+mod foo { pub struct Foo; impl Foo { pub fn foo() {} } }
+fn main() {
+ Foo::foo();
+}
+ ",
+ );
+ }
+
+ #[test]
+ fn assist_runs_on_enum_variant() {
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ let x = std::option::Option::Some$0(());
+}
+ ",
+ r"
+use std::option::Option::Some;
+
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ let x = Some(());
+}
+ ",
+ );
+
+ check_assist(
+ replace_qualified_name_with_use,
+ r"
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ std::option::Option;
+ let x: std::option::Option<()> = $0std::option::Option::Some(());
+}
+ ",
+ r"
+use std::option::Option;
+
+mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
+fn main() {
+ Option;
+ let x: Option<()> = Option::Some(());
}
",
);