rust/crates/assists/src/handlers/replace_qualified_name_with_use.rs
2020-08-13 17:33:38 +02:00

689 lines
13 KiB
Rust

use hir;
use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode};
use crate::{
utils::{find_insert_use_container, insert_use_statement},
AssistContext, AssistId, AssistKind, Assists,
};
// Assist: replace_qualified_name_with_use
//
// Adds a use statement for a given fully-qualified name.
//
// ```
// fn process(map: std::collections::<|>HashMap<String, String>) {}
// ```
// ->
// ```
// use std::collections::HashMap;
//
// fn process(map: HashMap<String, String>) {}
// ```
pub(crate) fn replace_qualified_name_with_use(
acc: &mut Assists,
ctx: &AssistContext,
) -> 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() {
return None;
}
let hir_path = ctx.sema.lower_path(&path)?;
let segments = collect_hir_path_segments(&hir_path)?;
if segments.len() < 2 {
return None;
}
let target = path.syntax().text_range();
acc.add(
AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
"Replace qualified path with use",
target,
|builder| {
let path_to_import = hir_path.mod_path().clone();
let container = match find_insert_use_container(path.syntax(), ctx) {
Some(c) => c,
None => return,
};
insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_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();
let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
shorten_paths(&mut rewriter, syntax, path);
builder.rewrite(rewriter);
},
)
}
fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
let mut ps = Vec::<SmolStr>::with_capacity(10);
match path.kind() {
hir::PathKind::Abs => ps.push("".into()),
hir::PathKind::Crate => ps.push("crate".into()),
hir::PathKind::Plain => {}
hir::PathKind::Super(0) => ps.push("self".into()),
hir::PathKind::Super(lvl) => {
let mut chain = "super".to_string();
for _ in 0..*lvl {
chain += "::super";
}
ps.push(chain.into());
}
hir::PathKind::DollarCrate(_) => return None,
}
ps.extend(path.segments().iter().map(|it| it.name.to_string().into()));
Some(ps)
}
/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, 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,
// 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.clone()),
}
},
_ => shorten_paths(rewriter, child, path.clone()),
}
}
}
}
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() {
rewriter.delete(parent.syntax());
}
if let Some(double_colon) = path.coloncolon_token() {
rewriter.delete(&double_colon);
}
Some(())
}
fn path_eq(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() => (),
_ => return false,
}
match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
(Some(lhs), Some(rhs)) => {
lhs_curr = lhs;
rhs_curr = rhs;
}
(None, None) => return true,
_ => return false,
}
}
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn test_replace_add_use_no_anchor() {
check_assist(
replace_qualified_name_with_use,
r"
std::fmt::Debug<|>
",
r"
use std::fmt::Debug;
Debug
",
);
}
#[test]
fn test_replace_add_use_no_anchor_with_item_below() {
check_assist(
replace_qualified_name_with_use,
r"
std::fmt::Debug<|>
fn main() {
}
",
r"
use std::fmt::Debug;
Debug
fn main() {
}
",
);
}
#[test]
fn test_replace_add_use_no_anchor_with_item_above() {
check_assist(
replace_qualified_name_with_use,
r"
fn main() {
}
std::fmt::Debug<|>
",
r"
use std::fmt::Debug;
fn main() {
}
Debug
",
);
}
#[test]
fn test_replace_add_use_no_anchor_2seg() {
check_assist(
replace_qualified_name_with_use,
r"
std::fmt<|>::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<|> for Foo {
}
",
r"
use stdx;
use std::fmt::Debug;
impl Debug for Foo {
}
",
);
}
#[test]
fn test_replace_file_use_other_anchor() {
check_assist(
replace_qualified_name_with_use,
r"
impl std::fmt::Debug<|> 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<|> 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<|> for Foo {
}
",
r"
use std::{io, fmt};
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<|> 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<|> 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<|> for Foo {
}
",
r"
use std::fmt::{Debug, nested::{Display, self}};
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<|> 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<|> for Foo {
}
",
r"
use std::fmt::{Debug, nested::{Display, Debug}};
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<|> for Foo {
}
",
r"
use std::fmt::{nested::Display, Debug};
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<|> 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<|>::trait_env() }
",
r"
use crate::{
ty::{Substs, Ty, lower},
AssocItem,
};
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<|> for Foo {
}
",
r"
use std::fmt as foo;
impl Debug for Foo {
}
",
);
}
#[test]
fn test_replace_not_applicable_one_segment() {
check_assist_not_applicable(
replace_qualified_name_with_use,
r"
impl foo<|> for Foo {
}
",
);
}
#[test]
fn test_replace_not_applicable_in_use() {
check_assist_not_applicable(
replace_qualified_name_with_use,
r"
use std::fmt<|>;
",
);
}
#[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<|>
}
}
",
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<|>
}
",
r"
#![allow(dead_code)]
use std::fmt::Debug;
fn main() {
Debug
}
",
);
}
#[test]
fn replaces_all_affected_paths() {
check_assist(
replace_qualified_name_with_use,
r"
fn main() {
std::fmt::Debug<|>;
let x: std::fmt::Debug = std::fmt::Debug;
}
",
r"
use std::fmt::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<|>;
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"
fn main() {
std::fmt::Debug<|>;
}
mod sub {
fn f() {
std::fmt::Debug;
}
}
",
r"
use std::fmt::Debug;
fn main() {
Debug;
}
mod sub {
fn f() {
std::fmt::Debug;
}
}
",
);
}
#[test]
fn does_not_replace_in_use() {
check_assist(
replace_qualified_name_with_use,
r"
use std::fmt::Display;
fn main() {
std::fmt<|>;
}
",
r"
use std::fmt::{self, Display};
fn main() {
fmt;
}
",
);
}
#[test]
fn does_not_replace_pub_use() {
check_assist(
replace_qualified_name_with_use,
r"
pub use std::fmt;
impl std::io<|> for Foo {
}
",
r"
use std::io;
pub use std::fmt;
impl io for Foo {
}
",
);
}
#[test]
fn does_not_replace_pub_crate_use() {
check_assist(
replace_qualified_name_with_use,
r"
pub(crate) use std::fmt;
impl std::io<|> for Foo {
}
",
r"
use std::io;
pub(crate) use std::fmt;
impl io for Foo {
}
",
);
}
}