use hir::{MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; use ra_ide_db::{ defs::{classify_name_ref, Definition, NameRefClass}, RootDatabase, }; use ra_syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T}; use crate::{ assist_context::{AssistBuilder, AssistContext, Assists}, AssistId, AssistKind, }; // Assist: expand_glob_import // // Expands glob imports. // // ``` // mod foo { // pub struct Bar; // pub struct Baz; // } // // use foo::*<|>; // // fn qux(bar: Bar, baz: Baz) {} // ``` // -> // ``` // mod foo { // pub struct Bar; // pub struct Baz; // } // // use foo::{Baz, Bar}; // // fn qux(bar: Bar, baz: Baz) {} // ``` pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let star = ctx.find_token_at_offset(T![*])?; let mod_path = find_mod_path(&star)?; let source_file = ctx.source_file(); let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); let defs_in_mod = find_defs_in_mod(ctx, scope, &mod_path)?; let name_refs_in_source_file = source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect(); let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file); let parent = star.parent().parent()?; acc.add( AssistId("expand_glob_import", AssistKind::RefactorRewrite), "Expand glob import", parent.text_range(), |builder| { replace_ast(builder, &parent, mod_path, used_names); }, ) } fn find_mod_path(star: &SyntaxToken) -> Option { let mut node = star.parent(); loop { match_ast! { match node { ast::UseTree(use_tree) => { if let Some(path) = use_tree.path() { return Some(path); } }, ast::UseTreeList(_use_tree_list) => {}, _ => return None, } } node = match node.parent() { Some(node) => node, None => return None, } } } #[derive(PartialEq)] enum Def { ModuleDef(ModuleDef), MacroDef(MacroDef), } impl Def { fn name(&self, db: &RootDatabase) -> Option { match self { Def::ModuleDef(def) => def.name(db), Def::MacroDef(def) => def.name(db), } } } fn find_defs_in_mod( ctx: &AssistContext, from: SemanticsScope<'_>, path: &ast::Path, ) -> Option> { let hir_path = ctx.sema.lower_path(&path)?; let module = if let Some(PathResolution::Def(ModuleDef::Module(module))) = from.resolve_hir_path_qualifier(&hir_path) { module } else { return None; }; let module_scope = module.scope(ctx.db(), from.module()); let mut defs = vec![]; for (_, def) in module_scope { match def { ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)), ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)), _ => continue, } } Some(defs) } fn find_used_names( ctx: &AssistContext, defs_in_mod: Vec, name_refs_in_source_file: Vec, ) -> Vec { let defs_in_source_file = name_refs_in_source_file .iter() .filter_map(|r| classify_name_ref(&ctx.sema, r)) .filter_map(|rc| match rc { NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), _ => None, }) .collect::>(); defs_in_mod .iter() .filter(|d| defs_in_source_file.contains(d)) .filter_map(|d| d.name(ctx.db())) .collect() } fn replace_ast( builder: &mut AssistBuilder, node: &SyntaxNode, path: ast::Path, used_names: Vec, ) { let new_use_tree_list = ast::make::use_tree_list(used_names.iter().map(|n| { ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) })); match_ast! { match node { ast::UseTree(use_tree) => { builder.replace_ast(use_tree, make_use_tree(path, new_use_tree_list)); }, ast::UseTreeList(use_tree_list) => { builder.replace_ast(use_tree_list, new_use_tree_list); }, ast::Use(use_item) => { builder.replace_ast(use_item, ast::make::use_item(make_use_tree(path, new_use_tree_list))); }, _ => {}, } } fn make_use_tree(path: ast::Path, use_tree_list: ast::UseTreeList) -> ast::UseTree { ast::make::use_tree(path, Some(use_tree_list), None, false) } } #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; #[test] fn expanding_glob_import() { check_assist( expand_glob_import, r" mod foo { pub struct Bar; pub struct Baz; pub struct Qux; pub fn f() {} } use foo::*<|>; fn qux(bar: Bar, baz: Baz) { f(); } ", r" mod foo { pub struct Bar; pub struct Baz; pub struct Qux; pub fn f() {} } use foo::{Baz, Bar, f}; fn qux(bar: Bar, baz: Baz) { f(); } ", ) } #[test] fn expanding_glob_import_with_existing_explicit_names() { check_assist( expand_glob_import, r" mod foo { pub struct Bar; pub struct Baz; pub struct Qux; pub fn f() {} } use foo::{*<|>, f}; fn qux(bar: Bar, baz: Baz) { f(); } ", r" mod foo { pub struct Bar; pub struct Baz; pub struct Qux; pub fn f() {} } use foo::{Baz, Bar, f}; fn qux(bar: Bar, baz: Baz) { f(); } ", ) } #[test] fn expanding_nested_glob_import() { check_assist( expand_glob_import, r" mod foo { mod bar { pub struct Bar; pub struct Baz; pub struct Qux; pub fn f() {} } mod baz { pub fn g() {} } } use foo::{bar::{*<|>, f}, baz::*}; fn qux(bar: Bar, baz: Baz) { f(); g(); } ", r" mod foo { mod bar { pub struct Bar; pub struct Baz; pub struct Qux; pub fn f() {} } mod baz { pub fn g() {} } } use foo::{bar::{Baz, Bar, f}, baz::*}; fn qux(bar: Bar, baz: Baz) { f(); g(); } ", ) } #[test] fn expanding_glob_import_with_macro_defs() { check_assist( expand_glob_import, r" //- /lib.rs crate:foo #[macro_export] macro_rules! bar { () => () } pub fn baz() {} //- /main.rs crate:main deps:foo use foo::*<|>; fn main() { bar!(); baz(); } ", r" use foo::{bar, baz}; fn main() { bar!(); baz(); } ", ) } #[test] fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() { check_assist_not_applicable( expand_glob_import, r" mod foo { pub struct Bar; pub struct Baz; pub struct Qux; } use foo::Bar<|>; fn qux(bar: Bar, baz: Baz) {} ", ) } }