about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2024-02-28 17:25:17 +0100
committerGuillaume Gomez <guillaume1.gomez@gmail.com>2024-03-09 12:43:18 +0100
commitf34804d807a8ca576c3a9096064a8b95ff7d0c2e (patch)
treebd2504b604ff250c9d3ee77f01e84af5f079624b
parent453242cbdea7c0e9288197393da706f45ba95b0a (diff)
downloadrust-f34804d807a8ca576c3a9096064a8b95ff7d0c2e.tar.gz
rust-f34804d807a8ca576c3a9096064a8b95ff7d0c2e.zip
Add new `duplicated_attributes` lint
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/attrs/duplicated_attributes.rs64
-rw-r--r--clippy_lints/src/attrs/mod.rs35
-rw-r--r--clippy_lints/src/declared_lints.rs1
4 files changed, 100 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e783b593f9..846b6b6b701 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5157,6 +5157,7 @@ Released 2018-09-13
 [`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
 [`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
 [`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
+[`duplicated_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes
 [`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
 [`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute
 [`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
diff --git a/clippy_lints/src/attrs/duplicated_attributes.rs b/clippy_lints/src/attrs/duplicated_attributes.rs
new file mode 100644
index 00000000000..3c5ac597fd5
--- /dev/null
+++ b/clippy_lints/src/attrs/duplicated_attributes.rs
@@ -0,0 +1,64 @@
+use super::DUPLICATED_ATTRIBUTES;
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_ast::{Attribute, MetaItem};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_lint::EarlyContext;
+use rustc_span::{sym, Span};
+use std::collections::hash_map::Entry;
+
+fn emit_if_duplicated(
+    cx: &EarlyContext<'_>,
+    attr: &MetaItem,
+    attr_paths: &mut FxHashMap<String, Span>,
+    complete_path: String,
+) {
+    match attr_paths.entry(complete_path) {
+        Entry::Vacant(v) => {
+            v.insert(attr.span);
+        },
+        Entry::Occupied(o) => {
+            span_lint_and_then(cx, DUPLICATED_ATTRIBUTES, attr.span, "duplicated attribute", |diag| {
+                diag.span_note(*o.get(), "first defined here");
+                diag.span_help(attr.span, "remove this attribute");
+            });
+        },
+    }
+}
+
+fn check_duplicated_attr(
+    cx: &EarlyContext<'_>,
+    attr: &MetaItem,
+    attr_paths: &mut FxHashMap<String, Span>,
+    parent: &mut Vec<String>,
+) {
+    let Some(ident) = attr.ident() else { return };
+    let name = ident.name;
+    if name == sym::doc || name == sym::cfg_attr {
+        // FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
+        // conditions are the same.
+        return;
+    }
+    if let Some(value) = attr.value_str() {
+        emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}={value}", parent.join(":")));
+    } else if let Some(sub_attrs) = attr.meta_item_list() {
+        parent.push(name.as_str().to_string());
+        for sub_attr in sub_attrs {
+            if let Some(meta) = sub_attr.meta_item() {
+                check_duplicated_attr(cx, meta, attr_paths, parent);
+            }
+        }
+        parent.pop();
+    } else {
+        emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.join(":")));
+    }
+}
+
+pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
+    let mut attr_paths = FxHashMap::default();
+
+    for attr in attrs {
+        if let Some(meta) = attr.meta() {
+            check_duplicated_attr(cx, &meta, &mut attr_paths, &mut Vec::new());
+        }
+    }
+}
diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs
index c4c65d3248a..675c428948f 100644
--- a/clippy_lints/src/attrs/mod.rs
+++ b/clippy_lints/src/attrs/mod.rs
@@ -4,6 +4,7 @@ mod allow_attributes_without_reason;
 mod blanket_clippy_restriction_lints;
 mod deprecated_cfg_attr;
 mod deprecated_semver;
+mod duplicated_attributes;
 mod empty_line_after;
 mod inline_always;
 mod maybe_misused_cfg;
@@ -16,7 +17,7 @@ mod useless_attribute;
 mod utils;
 
 use clippy_config::msrvs::Msrv;
-use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem};
+use rustc_ast::{Attribute, Crate, MetaItemKind, NestedMetaItem};
 use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 use rustc_session::{declare_lint_pass, impl_lint_pass};
@@ -489,6 +490,32 @@ declare_clippy_lint! {
     "item has both inner and outer attributes"
 }
 
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for attributes that appear two or more times.
+    ///
+    /// ### Why is this bad?
+    /// Repeating an attribute on the same item (or globally on the same crate)
+    /// is unnecessary and doesn't have an effect.
+    ///
+    /// ### Example
+    /// ```no_run
+    /// #[allow(dead_code)]
+    /// #[allow(dead_code)]
+    /// fn foo() {}
+    /// ```
+    ///
+    /// Use instead:
+    /// ```no_run
+    /// #[allow(dead_code)]
+    /// fn foo() {}
+    /// ```
+    #[clippy::version = "1.78.0"]
+    pub DUPLICATED_ATTRIBUTES,
+    suspicious,
+    "duplicated attribute"
+}
+
 declare_lint_pass!(Attributes => [
     ALLOW_ATTRIBUTES_WITHOUT_REASON,
     INLINE_ALWAYS,
@@ -568,12 +595,18 @@ impl_lint_pass!(EarlyAttributes => [
     DEPRECATED_CLIPPY_CFG_ATTR,
     UNNECESSARY_CLIPPY_CFG,
     MIXED_ATTRIBUTES_STYLE,
+    DUPLICATED_ATTRIBUTES,
 ]);
 
 impl EarlyLintPass for EarlyAttributes {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
+        duplicated_attributes::check(cx, &krate.attrs);
+    }
+
     fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
         empty_line_after::check(cx, item);
         mixed_attributes_style::check(cx, item);
+        duplicated_attributes::check(cx, &item.attrs);
     }
 
     fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 7a0d57c7859..62da9ffc330 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -54,6 +54,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::attrs::DEPRECATED_CFG_ATTR_INFO,
     crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
     crate::attrs::DEPRECATED_SEMVER_INFO,
+    crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
     crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
     crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
     crate::attrs::INLINE_ALWAYS_INFO,