]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_completion/src/completions/attribute.rs
Improve completion of cfg attributes
[rust.git] / crates / ide_completion / src / completions / attribute.rs
index 13d5b90c99ec82080c5bc0d397f650e3380c5970..cc4f4b2af728e973af0fcae689eddde843574fbb 100644 (file)
@@ -3,31 +3,37 @@
 //! 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};
 use once_cell::sync::Lazy;
 use rustc_hash::{FxHashMap, FxHashSet};
-use syntax::{ast, AstNode, NodeOrToken, SyntaxKind, T};
+use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, NodeOrToken, SyntaxKind, T};
 
 use crate::{
     context::CompletionContext,
-    generated_lint_completions::{CLIPPY_LINTS, FEATURES},
     item::{CompletionItem, CompletionItemKind, CompletionKind},
     Completions,
 };
 
+mod cfg;
 mod derive;
 mod lint;
-pub(crate) use self::lint::LintCompletion;
+mod repr;
 
 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),
             "allow" | "warn" | "deny" | "forbid" => {
-                lint::complete_lint(acc, ctx, token_tree.clone(), lint::DEFAULT_LINT_COMPLETIONS);
+                lint::complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINTS);
                 lint::complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
             }
+            "cfg" => {
+                cfg::complete_cfg(acc, ctx);
+            }
             _ => (),
         },
         (None, Some(_)) => (),
@@ -37,7 +43,13 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
 }
 
 fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
-    let attribute_annotated_item_kind = attribute.syntax().parent().map(|it| it.kind());
+    let is_inner = attribute.kind() == ast::AttrKind::Inner;
+    let attribute_annotated_item_kind =
+        attribute.syntax().parent().map(|it| it.kind()).filter(|_| {
+            is_inner
+            // If we got nothing coming after the attribute it could be anything so filter it the kind out
+                || non_trivia_sibling(attribute.syntax().clone().into(), Direction::Next).is_some()
+        });
     let attributes = attribute_annotated_item_kind.and_then(|kind| {
         if ast::Expr::can_cast(kind) {
             Some(EXPR_ATTRIBUTES)
@@ -45,7 +57,6 @@ fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attrib
             KIND_TO_ATTRIBUTES.get(&kind).copied()
         }
     });
-    let is_inner = attribute.kind() == ast::AttrKind::Inner;
 
     let add_completion = |attr_completion: &AttrCompletion| {
         let mut item = CompletionItem::new(
@@ -64,7 +75,7 @@ fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attrib
         }
 
         if is_inner || !attr_completion.prefer_inner {
-            acc.add(item.build());
+            item.add_to(acc);
         }
     };
 
@@ -77,6 +88,24 @@ 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 {
@@ -196,7 +225,7 @@ macro_rules! attrs {
 });
 const EXPR_ATTRIBUTES: &[&str] = attrs!();
 
-/// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index
+/// <https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index>
 // Keep these sorted for the binary search!
 const ATTRIBUTES: &[AttrCompletion] = &[
     attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
@@ -219,8 +248,7 @@ macro_rules! attrs {
     ),
     attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
     attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
-    // FIXME: resolve through macro resolution?
-    attr("global_allocator", None, None).prefer_inner(),
+    attr("global_allocator", None, None),
     attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
     attr("inline", Some("inline"), Some("inline")),
     attr("link", None, None),
@@ -239,7 +267,7 @@ macro_rules! attrs {
     attr("no_mangle", None, None),
     attr("no_std", None, None).prefer_inner(),
     attr("non_exhaustive", None, None),
-    attr("panic_handler", None, None).prefer_inner(),
+    attr("panic_handler", None, None),
     attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)),
     attr("proc_macro", None, None),
     attr("proc_macro_attribute", None, None),
@@ -300,7 +328,7 @@ mod tests {
 
     use expect_test::{expect, Expect};
 
-    use crate::{test_utils::completion_list, CompletionKind};
+    use crate::tests::completion_list;
 
     #[test]
     fn attributes_are_sorted() {
@@ -319,7 +347,7 @@ fn attributes_are_sorted() {
     }
 
     fn check(ra_fixture: &str, expect: Expect) {
-        let actual = completion_list(ra_fixture, CompletionKind::Attribute);
+        let actual = completion_list(ra_fixture);
         expect.assert_eq(&actual);
     }
 
@@ -609,6 +637,7 @@ fn complete_attribute_on_static() {
                 at export_name = "…"
                 at link_name = "…"
                 at link_section = "…"
+                at global_allocator
                 at used
             "#]],
         );
@@ -731,8 +760,45 @@ fn complete_attribute_on_variant() {
         );
     }
 
+    #[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#"
@@ -744,16 +810,61 @@ fn complete_attribute_on_expr() {
                 at warn(…)
             "#]],
         );
+    }
+
+    #[test]
+    fn complete_attribute_in_source_file_end() {
         check(
-            r#"fn main() { #[$0] foo(); }"#,
+            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
+"#]],
+        );
+    }
 }