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, + 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, + parent: &mut Vec, +) { + 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 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 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 @@ "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 @@ pub struct 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 @@ 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,