--- /dev/null
+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::{Bar, Baz};
+//
+// 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<ast::Path> {
+ 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<Name> {
+ 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<Vec<Def>> {
+ 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<Def>,
+ name_refs_in_source_file: Vec<ast::NameRef>,
+) -> Vec<Name> {
+ 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::<Vec<Def>>();
+
+ 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<Name>,
+) {
+ 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::UseItem(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) {}
+ ",
+ )
+ }
+}