]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_completion/src/completions/attribute.rs
fix: Don't duplicate attribute completions
[rust.git] / crates / ide_completion / src / completions / attribute.rs
index 9ee7f2bce6befc7300df4f9503151f3bfbed7d49..d642c8bc4df18bc0d4d18cd91713ebf7ed4246ee 100644 (file)
@@ -2,18 +2,21 @@
 //!
 //! This module uses a bit of static metadata to provide completions
 //! for built-in attributes.
-
-use hir::HasAttrs;
-use ide_db::helpers::generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES};
+//! Non-builtin attribute(excluding derives attributes) completions are done in [`super::unqualified_path`].
+
+use ide_db::{
+    helpers::{
+        generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES, RUSTDOC_LINTS},
+        parse_tt_as_comma_sep_paths,
+    },
+    SymbolKind,
+};
+use itertools::Itertools;
 use once_cell::sync::Lazy;
-use rustc_hash::{FxHashMap, FxHashSet};
-use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, NodeOrToken, SyntaxKind, T};
+use rustc_hash::FxHashMap;
+use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, T};
 
-use crate::{
-    context::CompletionContext,
-    item::{CompletionItem, CompletionItemKind, CompletionKind},
-    Completions,
-};
+use crate::{context::CompletionContext, item::CompletionItem, Completions};
 
 mod cfg;
 mod derive;
 
 pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
     let attribute = ctx.attribute_under_caret.as_ref()?;
-    match (attribute.path().and_then(|p| p.as_single_name_ref()), attribute.token_tree()) {
-        (Some(path), Some(token_tree)) => match path.text().as_str() {
-            "derive" => derive::complete_derive(acc, ctx, token_tree),
-            "repr" => repr::complete_repr(acc, ctx, token_tree),
-            "feature" => lint::complete_lint(acc, ctx, token_tree, FEATURES),
+    let name_ref = match attribute.path() {
+        Some(p) => Some(p.as_single_name_ref()?),
+        None => None,
+    };
+    match (name_ref, attribute.token_tree()) {
+        (Some(path), Some(tt)) if tt.l_paren_token().is_some() => match path.text().as_str() {
+            "repr" => repr::complete_repr(acc, ctx, tt),
+            "derive" => derive::complete_derive(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?),
+            "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES),
             "allow" | "warn" | "deny" | "forbid" => {
-                lint::complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINTS);
-                lint::complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
+                let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
+                lint::complete_lint(acc, ctx, &existing_lints, DEFAULT_LINTS);
+                lint::complete_lint(acc, ctx, &existing_lints, CLIPPY_LINTS);
+                lint::complete_lint(acc, ctx, &existing_lints, RUSTDOC_LINTS);
             }
             "cfg" => {
                 cfg::complete_cfg(acc, ctx);
             }
             _ => (),
         },
-        (None, Some(_)) => (),
-        _ => complete_new_attribute(acc, ctx, attribute),
+        (_, Some(_)) => (),
+        (_, None) => complete_new_attribute(acc, ctx, attribute),
     }
     Some(())
 }
@@ -59,12 +68,8 @@ fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attrib
     });
 
     let add_completion = |attr_completion: &AttrCompletion| {
-        let mut item = CompletionItem::new(
-            CompletionKind::Attribute,
-            ctx.source_range(),
-            attr_completion.label,
-        );
-        item.kind(CompletionItemKind::Attribute);
+        let mut item =
+            CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), attr_completion.label);
 
         if let Some(lookup) = attr_completion.lookup {
             item.lookup_by(lookup);
@@ -88,24 +93,6 @@ fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attrib
         None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
         None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
     }
-
-    // FIXME: write a test for this when we can
-    ctx.scope.process_all_names(&mut |name, scope_def| {
-        if let hir::ScopeDef::MacroDef(mac) = scope_def {
-            if mac.kind() == hir::MacroKind::Attr {
-                let mut item = CompletionItem::new(
-                    CompletionKind::Attribute,
-                    ctx.source_range(),
-                    name.to_string(),
-                );
-                item.kind(CompletionItemKind::Attribute);
-                if let Some(docs) = mac.docs(ctx.sema.db) {
-                    item.documentation(docs);
-                }
-                item.add_to(acc);
-            }
-        }
-    });
 }
 
 struct AttrCompletion {
@@ -168,7 +155,7 @@ macro_rules! attrs {
 #[rustfmt::skip]
 static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
     use SyntaxKind::*;
-    std::array::IntoIter::new([
+    [
         (
             SOURCE_FILE,
             attrs!(
@@ -220,7 +207,8 @@ macro_rules! attrs {
         (MATCH_ARM, attrs!()),
         (IDENT_PAT, attrs!()),
         (RECORD_PAT_FIELD, attrs!()),
-    ])
+    ]
+    .into_iter()
     .collect()
 });
 const EXPR_ATTRIBUTES: &[&str] = attrs!();
@@ -272,8 +260,12 @@ macro_rules! attrs {
     attr("proc_macro", None, None),
     attr("proc_macro_attribute", None, None),
     attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
-    attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
-        .prefer_inner(),
+    attr(
+        r#"recursion_limit = "…""#,
+        Some("recursion_limit"),
+        Some(r#"recursion_limit = "${0:128}""#),
+    )
+    .prefer_inner(),
     attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
     attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
     attr(
@@ -295,577 +287,35 @@ macro_rules! attrs {
     .prefer_inner(),
 ];
 
-fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Option<FxHashSet<String>> {
-    let (l_paren, r_paren) = derive_input.l_paren_token().zip(derive_input.r_paren_token())?;
-    let mut input_derives = FxHashSet::default();
-    let mut tokens = derive_input
+fn parse_comma_sep_expr(input: ast::TokenTree) -> Option<Vec<ast::Expr>> {
+    let r_paren = input.r_paren_token()?;
+    let tokens = input
         .syntax()
         .children_with_tokens()
-        .filter_map(NodeOrToken::into_token)
-        .skip_while(|token| token != &l_paren)
         .skip(1)
-        .take_while(|token| token != &r_paren)
-        .peekable();
-    let mut input = String::new();
-    while tokens.peek().is_some() {
-        for token in tokens.by_ref().take_while(|t| t.kind() != T![,]) {
-            input.push_str(token.text());
-        }
-
-        if !input.is_empty() {
-            input_derives.insert(input.trim().to_owned());
-        }
-
-        input.clear();
-    }
-
-    Some(input_derives)
+        .take_while(|it| it.as_token() != Some(&r_paren));
+    let input_expressions = tokens.into_iter().group_by(|tok| tok.kind() == T![,]);
+    Some(
+        input_expressions
+            .into_iter()
+            .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
+            .filter_map(|mut tokens| ast::Expr::parse(&tokens.join("")).ok())
+            .collect::<Vec<ast::Expr>>(),
+    )
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    use expect_test::{expect, Expect};
-
-    use crate::tests::completion_list;
-
-    #[test]
-    fn attributes_are_sorted() {
-        let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
-        let mut prev = attrs.next().unwrap();
-
-        attrs.for_each(|next| {
-            assert!(
-                prev < next,
-                r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#,
-                prev,
-                next
-            );
-            prev = next;
-        });
-    }
-
-    fn check(ra_fixture: &str, expect: Expect) {
-        let actual = completion_list(ra_fixture);
-        expect.assert_eq(&actual);
-    }
-
-    #[test]
-    fn test_attribute_completion_inside_nested_attr() {
-        check(r#"#[cfg($0)]"#, expect![[]])
-    }
-
-    #[test]
-    fn test_attribute_completion_with_existing_attr() {
-        check(
-            r#"#[no_mangle] #[$0] mcall!();"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-            "#]],
-        )
-    }
-
-    #[test]
-    fn complete_attribute_on_source_file() {
-        check(
-            r#"#![$0]"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at crate_name = ""
-                at feature(…)
-                at no_implicit_prelude
-                at no_main
-                at no_std
-                at recursion_limit = …
-                at type_length_limit = …
-                at windows_subsystem = "…"
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_module() {
-        check(
-            r#"#[$0] mod foo;"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at macro_use
-                at path = "…"
-            "#]],
-        );
-        check(
-            r#"mod foo {#![$0]}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at no_implicit_prelude
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_macro_rules() {
-        check(
-            r#"#[$0] macro_rules! foo {}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at macro_export
-                at macro_use
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_macro_def() {
-        check(
-            r#"#[$0] macro foo {}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_extern_crate() {
-        check(
-            r#"#[$0] extern crate foo;"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at macro_use
-            "#]],
+#[test]
+fn attributes_are_sorted() {
+    let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
+    let mut prev = attrs.next().unwrap();
+
+    attrs.for_each(|next| {
+        assert!(
+            prev < next,
+            r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#,
+            prev,
+            next
         );
-    }
-
-    #[test]
-    fn complete_attribute_on_use() {
-        check(
-            r#"#[$0] use foo;"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_type_alias() {
-        check(
-            r#"#[$0] type foo = ();"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_struct() {
-        check(
-            r#"#[$0] struct Foo;"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at derive(…)
-                at repr(…)
-                at non_exhaustive
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_enum() {
-        check(
-            r#"#[$0] enum Foo {}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at derive(…)
-                at repr(…)
-                at non_exhaustive
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_const() {
-        check(
-            r#"#[$0] const FOO: () = ();"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_static() {
-        check(
-            r#"#[$0] static FOO: () = ()"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at export_name = "…"
-                at link_name = "…"
-                at link_section = "…"
-                at global_allocator
-                at used
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_trait() {
-        check(
-            r#"#[$0] trait Foo {}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at must_use
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_impl() {
-        check(
-            r#"#[$0] impl () {}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at automatically_derived
-            "#]],
-        );
-        check(
-            r#"impl () {#![$0]}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_extern_block() {
-        check(
-            r#"#[$0] extern {}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at link
-            "#]],
-        );
-        check(
-            r#"extern {#![$0]}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at link
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_variant() {
-        check(
-            r#"enum Foo { #[$0] Bar }"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at non_exhaustive
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_fn() {
-        check(
-            r#"#[$0] fn main() {}"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-                at deprecated
-                at doc = "…"
-                at doc(hidden)
-                at doc(alias = "…")
-                at must_use
-                at no_mangle
-                at export_name = "…"
-                at link_name = "…"
-                at link_section = "…"
-                at cold
-                at ignore = "…"
-                at inline
-                at must_use
-                at panic_handler
-                at proc_macro
-                at proc_macro_derive(…)
-                at proc_macro_attribute
-                at should_panic
-                at target_feature = "…"
-                at test
-                at track_caller
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_on_expr() {
-        cov_mark::check!(no_keyword_completion_in_attr_of_expr);
-        check(
-            r#"fn main() { #[$0] foo() }"#,
-            expect![[r#"
-                at allow(…)
-                at cfg(…)
-                at cfg_attr(…)
-                at deny(…)
-                at forbid(…)
-                at warn(…)
-            "#]],
-        );
-    }
-
-    #[test]
-    fn complete_attribute_in_source_file_end() {
-        check(
-            r#"#[$0]"#,
-            expect![[r#"
-                at allow(…)
-                at automatically_derived
-                at cfg(…)
-                at cfg_attr(…)
-                at cold
-                at deny(…)
-                at deprecated
-                at derive(…)
-                at doc = "…"
-                at doc(alias = "…")
-                at doc(hidden)
-                at export_name = "…"
-                at forbid(…)
-                at global_allocator
-                at ignore = "…"
-                at inline
-                at link
-                at link_name = "…"
-                at link_section = "…"
-                at macro_export
-                at macro_use
-                at must_use
-                at no_mangle
-                at non_exhaustive
-                at panic_handler
-                at path = "…"
-                at proc_macro
-                at proc_macro_attribute
-                at proc_macro_derive(…)
-                at repr(…)
-                at should_panic
-                at target_feature = "…"
-                at test
-                at track_caller
-                at used
-                at warn(…)
-            "#]],
-        );
-    }
-
-    #[test]
-    fn test_cfg() {
-        check(
-            r#"#[cfg(target_endian = $0"#,
-            expect![[r#"
-                at little
-                at big
-"#]],
-        );
-    }
+        prev = next;
+    });
 }