]> git.lizzy.rs Git - rust.git/commitdiff
Add expand glob import assist
authorunexge <unexge@gmail.com>
Sun, 2 Aug 2020 19:56:54 +0000 (22:56 +0300)
committerunexge <unexge@gmail.com>
Sun, 2 Aug 2020 19:56:54 +0000 (22:56 +0300)
crates/ra_assists/src/assist_context.rs
crates/ra_assists/src/handlers/expand_glob_import.rs [new file with mode: 0644]
crates/ra_assists/src/lib.rs
crates/ra_syntax/src/ast/make.rs

index 3407df8562d2580635777f6f85ebb15dd0c70439..afd3fd4b9e37c8d8211aed5cf0b675b316506f7b 100644 (file)
@@ -73,6 +73,10 @@ pub(crate) fn db(&self) -> &RootDatabase {
         self.sema.db
     }
 
+    pub(crate) fn source_file(&self) -> &SourceFile {
+        &self.source_file
+    }
+
     // NB, this ignores active selection.
     pub(crate) fn offset(&self) -> TextSize {
         self.frange.range.start()
diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs
new file mode 100644 (file)
index 0000000..5902393
--- /dev/null
@@ -0,0 +1,359 @@
+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) {}
+    ",
+        )
+    }
+}
index 465b90415171093f0b83f78affe9b26cb28d1122..507646cc802899573e1b8a1807b43b5fb4ea0fb1 100644 (file)
@@ -140,6 +140,7 @@ mod handlers {
     mod change_return_type_to_result;
     mod change_visibility;
     mod early_return;
+    mod expand_glob_import;
     mod extract_struct_from_enum_variant;
     mod extract_variable;
     mod fill_match_arms;
@@ -181,6 +182,7 @@ pub(crate) fn all() -> &'static [Handler] {
             change_return_type_to_result::change_return_type_to_result,
             change_visibility::change_visibility,
             early_return::convert_to_guarded_return,
+            expand_glob_import::expand_glob_import,
             extract_struct_from_enum_variant::extract_struct_from_enum_variant,
             extract_variable::extract_variable,
             fill_match_arms::fill_match_arms,
index 6737770159ddb32997fbbfa06a220f3538ab31b3..3cb1d35eeca51fde9290661fed1ec49a94d72a6b 100644 (file)
@@ -30,7 +30,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
 pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
     path_from_text(&format!("{}::{}", qual, segment))
 }
-fn path_from_text(text: &str) -> ast::Path {
+pub fn path_from_text(text: &str) -> ast::Path {
     ast_from_text(text)
 }