//! 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(_)) => (),
}
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)
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(
}
if is_inner || !attr_completion.prefer_inner {
- acc.add(item.build());
+ item.add_to(acc);
}
};
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 {
});
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})")),
use expect_test::{expect, Expect};
- use crate::{test_utils::completion_list, CompletionKind};
+ use crate::tests::completion_list;
#[test]
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);
}
#[test]
fn complete_attribute_on_expr() {
+ cov_mark::check!(no_keyword_completion_in_attr_of_expr);
check(
r#"fn main() { #[$0] foo() }"#,
expect![[r#"
"#]],
);
}
+
+ #[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
+"#]],
+ );
+ }
}