]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_assists/src/handlers/auto_import.rs
Merge #11481
[rust.git] / crates / ide_assists / src / handlers / auto_import.rs
index 506cc292c4124b89cf65a3bfe9a399d9077ff009..cac736ff850a0f8fe41fbd11b355fdde577c4885 100644 (file)
@@ -3,7 +3,7 @@
     insert_use::{insert_use, ImportScope},
     mod_path_to_ast,
 };
-use syntax::{ast, AstNode, SyntaxNode};
+use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxElement};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
 
 // use super::AssistContext;
 // ```
 //
-// .Merge Behavior
+// .Import Granularity
 //
-// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting.
+// It is possible to configure how use-trees are merged with the `importGranularity` setting.
 // It has the following configurations:
 //
-// - `full`: This setting will cause auto-import to always completely merge use-trees that share the
-//  same path prefix while also merging inner trees that share the same path-prefix. This kind of
+// - `crate`: Merge imports from the same crate into a single use statement. This kind of
 //  nesting is only supported in Rust versions later than 1.24.
-// - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree
-//  will only contain a nesting of single segment paths at the very end.
-// - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple
-//  paths.
+// - `module`: Merge imports from the same module into a single use statement.
+// - `item`: Don't merge imports at all, creating one import per item.
+// - `preserve`: Do not change the granularity of any imports. For auto-import this has the same
+//  effect as `item`.
 //
-// In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehavior`.
+// In `VS Code` the configuration for this is `rust-analyzer.assist.importGranularity`.
 //
 // .Import Prefix
 //
 // ```
 pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
-    let proposed_imports =
+    let mut proposed_imports =
         import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
     if proposed_imports.is_empty() {
         return None;
     }
 
-    let range = ctx.sema.original_range(&syntax_under_caret).range;
+    let range = match &syntax_under_caret {
+        NodeOrToken::Node(node) => ctx.sema.original_range(node).range,
+        NodeOrToken::Token(token) => token.text_range(),
+    };
     let group_label = group_label(import_assets.import_candidate());
-    let scope = ImportScope::find_insert_use_container_with_macros(&syntax_under_caret, &ctx.sema)?;
+    let scope = ImportScope::find_insert_use_container(
+        &match syntax_under_caret {
+            NodeOrToken::Node(it) => it,
+            NodeOrToken::Token(it) => it.parent()?,
+        },
+        &ctx.sema,
+    )?;
+
+    // we aren't interested in different namespaces
+    proposed_imports.dedup_by(|a, b| a.import_path == b.import_path);
     for import in proposed_imports {
         acc.add_group(
             &group_label,
@@ -102,27 +113,35 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
             range,
             |builder| {
                 let scope = match scope.clone() {
-                    ImportScope::File(it) => ImportScope::File(builder.make_ast_mut(it)),
-                    ImportScope::Module(it) => ImportScope::Module(builder.make_ast_mut(it)),
+                    ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
+                    ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
+                    ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
                 };
-                insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use);
+                insert_use(&scope, mod_path_to_ast(&import.import_path), &ctx.config.insert_use);
             },
         );
     }
     Some(())
 }
 
-pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets, SyntaxNode)> {
+pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets, SyntaxElement)> {
     if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
         ImportAssets::for_exact_path(&path_under_caret, &ctx.sema)
-            .zip(Some(path_under_caret.syntax().clone()))
+            .zip(Some(path_under_caret.syntax().clone().into()))
     } else if let Some(method_under_caret) =
         ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
     {
         ImportAssets::for_method_call(&method_under_caret, &ctx.sema)
-            .zip(Some(method_under_caret.syntax().clone()))
+            .zip(Some(method_under_caret.syntax().clone().into()))
+    } else if let Some(pat) = ctx
+        .find_node_at_offset_with_descend::<ast::IdentPat>()
+        .filter(ast::IdentPat::is_simple_ident)
+    {
+        ImportAssets::for_ident_pat(&ctx.sema, &pat).zip(Some(pat.syntax().clone().into()))
     } else {
-        None
+        // FIXME: Descend?
+        let ident = ctx.find_token_at_offset()?;
+        ImportAssets::for_derive_ident(&ctx.sema, &ident).zip(Some(ident.syntax().clone().into()))
     }
 }
 
@@ -142,8 +161,63 @@ fn group_label(import_candidate: &ImportCandidate) -> GroupLabel {
 #[cfg(test)]
 mod tests {
     use super::*;
+
     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
 
+    #[test]
+    fn not_applicable_if_scope_inside_macro() {
+        check_assist_not_applicable(
+            auto_import,
+            r"
+mod bar {
+    pub struct Baz;
+}
+macro_rules! foo {
+    ($it:ident) => {
+        mod __ {
+            fn __(x: $it) {}
+        }
+    };
+}
+foo! {
+    Baz$0
+}
+",
+        );
+    }
+
+    #[test]
+    fn applicable_in_attributes() {
+        check_assist(
+            auto_import,
+            r"
+//- proc_macros: identity
+#[proc_macros::identity]
+mod foo {
+    mod bar {
+        const _: Baz$0 = ();
+    }
+}
+mod baz {
+    pub struct Baz;
+}
+",
+            r"
+#[proc_macros::identity]
+mod foo {
+    mod bar {
+        use crate::baz::Baz;
+
+        const _: Baz = ();
+    }
+}
+mod baz {
+    pub struct Baz;
+}
+",
+        );
+    }
+
     #[test]
     fn applicable_when_found_an_import_partial() {
         check_assist(
@@ -972,6 +1046,7 @@ fn bar() {
 
     #[test]
     fn uses_abs_path_with_extern_crate_clash() {
+        cov_mark::check!(ambiguous_crate_start);
         check_assist(
             auto_import,
             r#"
@@ -992,6 +1067,57 @@ mod foo {}
 const _: () = {
     Foo
 };
+"#,
+        );
+    }
+
+    #[test]
+    fn works_on_ident_patterns() {
+        check_assist(
+            auto_import,
+            r#"
+mod foo {
+    pub struct Foo {}
+}
+fn foo() {
+    let Foo$0;
+}
+"#,
+            r#"
+use foo::Foo;
+
+mod foo {
+    pub struct Foo {}
+}
+fn foo() {
+    let Foo;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn works_in_derives() {
+        check_assist(
+            auto_import,
+            r#"
+//- minicore:derive
+mod foo {
+    #[rustc_builtin_macro]
+    pub macro Copy {}
+}
+#[derive(Copy$0)]
+struct Foo;
+"#,
+            r#"
+use foo::Copy;
+
+mod foo {
+    #[rustc_builtin_macro]
+    pub macro Copy {}
+}
+#[derive(Copy)]
+struct Foo;
 "#,
         );
     }