use syntax::{
AstNode, SyntaxKind,
ast::{self, HasAttrs, HasVisibility, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
syntax_editor::{Element, Position, Removable},
};
use crate::{
AssistId,
assist_context::{AssistContext, Assists},
};
// Assist: unmerge_imports
//
// Extracts a use item from a use list into a standalone use list.
//
// ```
// use std::fmt::{Debug, Display$0};
// ```
// ->
// ```
// use std::fmt::{Debug};
// use std::fmt::Display;
// ```
pub(crate) fn unmerge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let tree = ctx.find_node_at_offset::<ast::UseTree>()?;
let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
if tree_list.use_trees().count() < 2 {
cov_mark::hit!(skip_single_import);
return None;
}
let use_ = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
let path = resolve_full_path(&tree)?;
// If possible, explain what is going to be done.
let label = match tree.path().and_then(|path| path.first_segment()) {
Some(name) => format!("Unmerge use of `{name}`"),
None => "Unmerge use".into(),
};
let target = tree.syntax().text_range();
acc.add(AssistId::refactor_rewrite("unmerge_imports"), label, target, |builder| {
let make = SyntaxFactory::with_mappings();
let new_use = make.use_(
use_.attrs(),
use_.visibility(),
make.use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
);
let mut editor = builder.make_editor(use_.syntax());
// Remove the use tree from the current use item
tree.remove(&mut editor);
// Insert a newline and indentation, followed by the new use item
editor.insert_all(
Position::after(use_.syntax()),
vec![
make.whitespace(&format!("\n{}", IndentLevel::from_node(use_.syntax())))
.syntax_element(),
new_use.syntax().syntax_element(),
],
);
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.vfs_file_id(), editor);
})
}
fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
let paths = tree
.syntax()
.ancestors()
.take_while(|n| n.kind() != SyntaxKind::USE)
.filter_map(ast::UseTree::cast)
.filter_map(|t| t.path());
let final_path = paths.reduce(|prev, next| make::path_concat(next, prev))?;
if final_path.segment().is_some_and(|it| it.self_token().is_some()) {
final_path.qualifier()
} else {
Some(final_path)
}
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn skip_single_import() {
cov_mark::check!(skip_single_import);
check_assist_not_applicable(
unmerge_imports,
r"
use std::fmt::Debug$0;
",
);
check_assist_not_applicable(
unmerge_imports,
r"
use std::fmt::{Debug$0};
",
);
check_assist_not_applicable(
unmerge_imports,
r"
use std::fmt::Debug as Dbg$0;
",
);
}
#[test]
fn skip_single_glob_import() {
check_assist_not_applicable(
unmerge_imports,
r"
use std::fmt::*$0;
",
);
}
#[test]
fn unmerge_import() {
check_assist(
unmerge_imports,
r"
use std::fmt::{Debug, Display$0};
",
r"
use std::fmt::{Debug};
use std::fmt::Display;
",
);
check_assist(
unmerge_imports,
r"
use std::fmt::{Debug, format$0, Display};
",
r"
use std::fmt::{Debug, Display};
use std::fmt::format;
",
);
}
#[test]
fn unmerge_glob_import() {
check_assist(
unmerge_imports,
r"
use std::fmt::{*$0, Display};
",
r"
use std::fmt::{Display};
use std::fmt::*;
",
);
}
#[test]
fn unmerge_renamed_import() {
check_assist(
unmerge_imports,
r"
use std::fmt::{Debug, Display as Disp$0};
",
r"
use std::fmt::{Debug};
use std::fmt::Display as Disp;
",
);
}
#[test]
fn unmerge_indented_import() {
check_assist(
unmerge_imports,
r"
mod format {
use std::fmt::{Debug, Display$0 as Disp, format};
}
",
r"
mod format {
use std::fmt::{Debug, format};
use std::fmt::Display as Disp;
}
",
);
}
#[test]
fn unmerge_nested_import() {
check_assist(
unmerge_imports,
r"
use foo::bar::{baz::{qux$0, foobar}, barbaz};
",
r"
use foo::bar::{baz::{foobar}, barbaz};
use foo::bar::baz::qux;
",
);
check_assist(
unmerge_imports,
r"
use foo::bar::{baz$0::{qux, foobar}, barbaz};
",
r"
use foo::bar::{barbaz};
use foo::bar::baz::{qux, foobar};
",
);
}
#[test]
fn unmerge_import_with_visibility() {
check_assist(
unmerge_imports,
r"
pub use std::fmt::{Debug, Display$0};
",
r"
pub use std::fmt::{Debug};
pub use std::fmt::Display;
",
);
}
#[test]
fn unmerge_import_on_self() {
check_assist(
unmerge_imports,
r"use std::process::{Command, self$0};",
r"use std::process::{Command};
use std::process;",
);
}
#[test]
fn unmerge_import_with_attributes() {
check_assist(
unmerge_imports,
r"
#[allow(deprecated)]
use foo::{bar, baz$0};",
r"
#[allow(deprecated)]
use foo::{bar};
#[allow(deprecated)]
use foo::baz;",
);
}
}