-use ide_db::helpers::insert_use::{insert_use, ImportScope};
-use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
+use hir::AsAssocItem;
+use ide_db::helpers::{
+ insert_use::{insert_use, ImportScope},
+ mod_path_to_ast,
+};
+use syntax::{
+ ast::{self, make},
+ match_ast, ted, AstNode, SyntaxNode,
+};
use crate::{AssistContext, AssistId, AssistKind, Assists};
// Adds a use statement for a given fully-qualified name.
//
// ```
+// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
// fn process(map: std::collections::$0HashMap<String, String>) {}
// ```
// ->
// ```
// use std::collections::HashMap;
//
+// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
// fn process(map: HashMap<String, String>) {}
// ```
pub(crate) fn replace_qualified_name_with_use(
) -> Option<()> {
let path: ast::Path = ctx.find_node_at_offset()?;
// We don't want to mess with use statements
- if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
+ if path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
+ cov_mark::hit!(not_applicable_in_use);
return None;
}
+
if path.qualifier().is_none() {
cov_mark::hit!(dont_import_trivial_paths);
return None;
}
- let target = path.syntax().text_range();
+ // only offer replacement for non assoc items
+ match ctx.sema.resolve_path(&path)? {
+ hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (),
+ hir::PathResolution::Macro(_) => (),
+ _ => 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(&path.first_qualifier_or_self())? {
+ hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module,
+ _ => return None,
+ };
+
+ let starts_with_name_ref = !matches!(
+ path.first_segment().and_then(|it| it.kind()),
+ Some(
+ ast::PathSegmentKind::CrateKw
+ | ast::PathSegmentKind::SuperKw
+ | ast::PathSegmentKind::SelfKw
+ )
+ );
+ let path_to_qualifier = starts_with_name_ref
+ .then(|| {
+ ctx.sema.scope(path.syntax()).module().and_then(|m| {
+ m.find_use_path_prefixed(ctx.sema.db, module, ctx.config.insert_use.prefix_kind)
+ })
+ })
+ .flatten();
+
let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
- let syntax = scope.as_syntax_node();
+ let target = path.syntax().text_range();
acc.add(
AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
"Replace qualified path 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 mut rewriter = SyntaxRewriter::default();
- shorten_paths(&mut rewriter, syntax.clone(), &path);
- if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
- rewriter += insert_use(import_scope, path, ctx.config.insert_use);
- builder.rewrite(rewriter);
- }
+ let scope = match scope {
+ ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+ ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+ ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
+ };
+ shorten_paths(scope.as_syntax_node(), &path.clone_for_update());
+ // stick the found import in front of the to be replaced path
+ let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
+ Some(qualifier) => make::path_concat(qualifier, path),
+ None => path,
+ };
+ insert_use(&scope, path, &ctx.config.insert_use);
},
)
}
/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
-fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) {
+fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
for child in node.children() {
match_ast! {
match child {
// Don't modify `use` items, as this can break the `use` item when injecting a new
// import into the use tree.
- ast::Use(_it) => continue,
+ ast::Use(_) => continue,
// Don't descend into submodules, they don't have the same `use` items in scope.
- ast::Module(_it) => continue,
-
- ast::Path(p) => {
- match maybe_replace_path(rewriter, p.clone(), path.clone()) {
- Some(()) => {},
- None => shorten_paths(rewriter, p.syntax().clone(), path),
- }
+ // 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);
},
- _ => shorten_paths(rewriter, child, path),
+ _ => shorten_paths(&child, path),
}
}
}
}
-fn maybe_replace_path(
- rewriter: &mut SyntaxRewriter<'static>,
- path: ast::Path,
- target: ast::Path,
-) -> Option<()> {
- if !path_eq(path.clone(), target) {
+fn maybe_replace_path(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() {
- rewriter.delete(parent.syntax());
+ ted::remove(parent.syntax());
}
if let Some(double_colon) = path.coloncolon_token() {
- rewriter.delete(&double_colon);
+ ted::remove(&double_colon);
}
Some(())
}
-fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
+fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
let mut lhs_curr = lhs;
let mut rhs_curr = rhs;
loop {
- match (lhs_curr.segment(), rhs_curr.segment()) {
- (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
+ match lhs_curr.segment().zip(rhs_curr.segment()) {
+ Some((lhs, rhs))
+ if lhs.coloncolon_token().is_some() == rhs.coloncolon_token().is_some()
+ && lhs
+ .name_ref()
+ .zip(rhs.name_ref())
+ .map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
_ => return false,
}
fn test_replace_already_imported() {
check_assist(
replace_qualified_name_with_use,
- r"use std::fs;
+ r"
+mod std { pub mod fs { pub struct Path; } }
+use std::fs;
fn main() {
std::f$0s::Path
}",
- r"use std::fs;
+ r"
+mod std { pub mod fs { pub struct Path; } }
+use std::fs;
fn main() {
fs::Path
check_assist(
replace_qualified_name_with_use,
r"
-std::fmt::Debug$0
+mod std { pub mod fs { pub struct Path; } }
+std::fs::Path$0
",
r"
-use std::fmt::Debug;
+use std::fs::Path;
-Debug
- ",
- );
- }
- #[test]
- fn test_replace_add_use_no_anchor_with_item_below() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-std::fmt::Debug$0
-
-fn main() {
-}
- ",
- r"
-use std::fmt::Debug;
-
-Debug
-
-fn main() {
-}
+mod std { pub mod fs { pub struct Path; } }
+Path
",
);
}
#[test]
- fn test_replace_add_use_no_anchor_with_item_above() {
+ fn test_replace_add_use_no_anchor_middle_segment() {
check_assist(
replace_qualified_name_with_use,
r"
-fn main() {
-}
-
-std::fmt::Debug$0
+mod std { pub mod fs { pub struct Path; } }
+std::fs$0::Path
",
r"
-use std::fmt::Debug;
+use std::fs;
-fn main() {
-}
-
-Debug
+mod std { pub mod fs { pub struct Path; } }
+fs::Path
",
);
}
- #[test]
- fn test_replace_add_use_no_anchor_2seg() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-std::fmt$0::Debug
- ",
- r"
-use std::fmt;
-
-fmt::Debug
- ",
- );
- }
-
- #[test]
- fn test_replace_add_use() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use stdx;
-
-impl std::fmt::Debug$0 for Foo {
-}
- ",
- r"
-use std::fmt::Debug;
-
-use stdx;
-
-impl Debug for Foo {
-}
- ",
- );
- }
-
- #[test]
- fn test_replace_file_use_other_anchor() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-impl std::fmt::Debug$0 for Foo {
-}
- ",
- r"
-use std::fmt::Debug;
-
-impl Debug for Foo {
-}
- ",
- );
- }
-
- #[test]
- fn test_replace_add_use_other_anchor_indent() {
- check_assist(
- replace_qualified_name_with_use,
- r"
- impl std::fmt::Debug$0 for Foo {
- }
- ",
- r"
- use std::fmt::Debug;
-
- impl Debug for Foo {
- }
- ",
- );
- }
-
- #[test]
- fn test_replace_split_different() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt;
-
-impl std::io$0 for Foo {
-}
- ",
- r"
-use std::{fmt, io};
-
-impl io for Foo {
-}
- ",
- );
- }
-
- #[test]
- fn test_replace_split_self_for_use() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt;
-
-impl std::fmt::Debug$0 for Foo {
-}
- ",
- r"
-use std::fmt::{self, Debug};
-
-impl Debug for Foo {
-}
- ",
- );
- }
-
- #[test]
- fn test_replace_split_self_for_target() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt::Debug;
-
-impl std::fmt$0 for Foo {
-}
- ",
- r"
-use std::fmt::{self, Debug};
-
-impl fmt for Foo {
-}
- ",
- );
- }
-
- #[test]
- fn test_replace_add_to_nested_self_nested() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt::{Debug, nested::{Display}};
-
-impl std::fmt::nested$0 for Foo {
-}
-",
- r"
-use std::fmt::{Debug, nested::{self, Display}};
-
-impl nested for Foo {
-}
-",
- );
- }
-
- #[test]
- fn test_replace_add_to_nested_self_already_included() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt::{Debug, nested::{self, Display}};
-
-impl std::fmt::nested$0 for Foo {
-}
-",
- r"
-use std::fmt::{Debug, nested::{self, Display}};
-
-impl nested for Foo {
-}
-",
- );
- }
-
- #[test]
- fn test_replace_add_to_nested_nested() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt::{Debug, nested::{Display}};
-
-impl std::fmt::nested::Debug$0 for Foo {
-}
-",
- r"
-use std::fmt::{Debug, nested::{Debug, Display}};
-
-impl Debug for Foo {
-}
-",
- );
- }
-
- #[test]
- fn test_replace_split_common_target_longer() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt::Debug;
-
-impl std::fmt::nested::Display$0 for Foo {
-}
-",
- r"
-use std::fmt::{Debug, nested::Display};
-
-impl Display for Foo {
-}
-",
- );
- }
-
- #[test]
- fn test_replace_split_common_use_longer() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt::nested::Debug;
-
-impl std::fmt::Display$0 for Foo {
-}
-",
- r"
-use std::fmt::{Display, nested::Debug};
-
-impl Display for Foo {
-}
-",
- );
- }
-
- #[test]
- fn test_replace_use_nested_import() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use crate::{
- ty::{Substs, Ty},
- AssocItem,
-};
-
-fn foo() { crate::ty::lower$0::trait_env() }
-",
- r"
-use crate::{AssocItem, ty::{Substs, Ty, lower}};
-
-fn foo() { lower::trait_env() }
-",
- );
- }
-
- #[test]
- fn test_replace_alias() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-use std::fmt as foo;
-
-impl foo::Debug$0 for Foo {
-}
-",
- r"
-use std::fmt as foo;
-
-use foo::Debug;
-
-impl Debug for Foo {
-}
-",
- );
- }
-
#[test]
fn dont_import_trivial_paths() {
cov_mark::check!(dont_import_trivial_paths);
- check_assist_not_applicable(
- replace_qualified_name_with_use,
- r"
-impl foo$0 for Foo {
-}
-",
- );
+ check_assist_not_applicable(replace_qualified_name_with_use, r"impl foo$0 for () {}");
}
#[test]
fn test_replace_not_applicable_in_use() {
- check_assist_not_applicable(
- replace_qualified_name_with_use,
- r"
-use std::fmt$0;
-",
- );
- }
-
- #[test]
- fn test_replace_add_use_no_anchor_in_mod_mod() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-mod foo {
- mod bar {
- std::fmt::Debug$0
- }
-}
- ",
- r"
-mod foo {
- mod bar {
- use std::fmt::Debug;
-
- Debug
- }
-}
- ",
- );
- }
-
- #[test]
- fn inserts_imports_after_inner_attributes() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-#![allow(dead_code)]
-
-fn main() {
- std::fmt::Debug$0
-}
- ",
- r"
-#![allow(dead_code)]
-
-use std::fmt::Debug;
-
-fn main() {
- Debug
-}
- ",
- );
+ cov_mark::check!(not_applicable_in_use);
+ check_assist_not_applicable(replace_qualified_name_with_use, r"use std::fmt$0;");
}
#[test]
check_assist(
replace_qualified_name_with_use,
r"
+mod std { pub mod fmt { pub trait Debug {} } }
fn main() {
std::fmt::Debug$0;
let x: std::fmt::Debug = std::fmt::Debug;
r"
use std::fmt::Debug;
+mod std { pub mod fmt { pub trait Debug {} } }
fn main() {
Debug;
let x: Debug = Debug;
);
}
- #[test]
- fn replaces_all_affected_paths_mod() {
- check_assist(
- replace_qualified_name_with_use,
- r"
-mod m {
- fn f() {
- std::fmt::Debug$0;
- let x: std::fmt::Debug = std::fmt::Debug;
- }
- fn g() {
- std::fmt::Debug;
- }
-}
-
-fn f() {
- std::fmt::Debug;
-}
- ",
- r"
-mod m {
- use std::fmt::Debug;
-
- fn f() {
- Debug;
- let x: Debug = Debug;
- }
- fn g() {
- Debug;
- }
-}
-
-fn f() {
- std::fmt::Debug;
-}
- ",
- );
- }
-
#[test]
fn does_not_replace_in_submodules() {
check_assist(
replace_qualified_name_with_use,
r"
+mod std { pub mod fmt { pub trait Debug {} } }
fn main() {
std::fmt::Debug$0;
}
r"
use std::fmt::Debug;
+mod std { pub mod fmt { pub trait Debug {} } }
fn main() {
Debug;
}
check_assist(
replace_qualified_name_with_use,
r"
+mod std { pub mod fmt { pub trait Display {} } }
use std::fmt::Display;
fn main() {
}
",
r"
-use std::fmt::{self, Display};
+mod std { pub mod fmt { pub trait Display {} } }
+use std::fmt::{Display, self};
fn main() {
fmt;
}
#[test]
- fn does_not_replace_pub_use() {
+ fn does_not_replace_assoc_item_path() {
+ check_assist_not_applicable(
+ replace_qualified_name_with_use,
+ r"
+pub struct Foo;
+impl Foo {
+ pub fn foo() {}
+}
+
+fn main() {
+ Foo::foo$0();
+}
+",
+ );
+ }
+
+ #[test]
+ fn replace_reuses_path_qualifier() {
check_assist(
replace_qualified_name_with_use,
r"
-pub use std::fmt;
+pub mod foo {
+ pub struct Foo;
+}
-impl std::io$0 for Foo {
+mod bar {
+ pub use super::foo::Foo as Bar;
}
- ",
+
+fn main() {
+ foo::Foo$0;
+}
+",
r"
-pub use std::fmt;
-use std::io;
+use foo::Foo;
-impl io for Foo {
+pub mod foo {
+ pub struct Foo;
}
- ",
+
+mod bar {
+ pub use super::foo::Foo as Bar;
+}
+
+fn main() {
+ Foo;
+}
+",
);
}
#[test]
- fn does_not_replace_pub_crate_use() {
+ fn replace_does_not_always_try_to_replace_by_full_item_path() {
check_assist(
replace_qualified_name_with_use,
r"
-pub(crate) use std::fmt;
+use std::mem;
-impl std::io$0 for Foo {
+mod std {
+ pub mod mem {
+ pub fn drop<T>(_: T) {}
+ }
}
- ",
+
+fn main() {
+ mem::drop$0(0);
+}
+",
r"
-pub(crate) use std::fmt;
-use std::io;
+use std::mem::{self, drop};
+
+mod std {
+ pub mod mem {
+ pub fn drop<T>(_: T) {}
+ }
+}
-impl io for Foo {
+fn main() {
+ drop(0);
}
- ",
+",
);
}
}