Skip to content

Commit f685a4b

Browse files
committed
Auto merge of #12378 - GuillaumeGomez:duplicated_attr, r=blyxyas
Add new `duplicated_attributes` lint It's a lint idea that `@llogiq` gave me while reviewing another PR. There are some limitations, in particular for the "output". Initially I wanted to make it possible for directly lint against the whole attribute if its parts were all duplicated, but then I realized that the output would be chaotic if the duplicates were coming from different attributes, so I preferred to go to the simplest way and simply emit a warning for each entry. Not the best, but makes the implementation much easier. Another limitation is that `cfg_attr` would be a bit more tricky to implement because we need to check if two `cfg` sets are exactly the same. I added a FIXME and will likely come back to it later. And finally, I updated the `cargo dev update_lints` command because the generated `tests/ui/rename.rs` file was emitting the `duplicated_attributes` lint, so I allowed this lint inside it to prevent it from working. changelog: Add new `duplicated_attributes` lint
2 parents 870e016 + ae52a9d commit f685a4b

16 files changed

+311
-67
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5157,6 +5157,7 @@ Released 2018-09-13
51575157
[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
51585158
[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
51595159
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
5160+
[`duplicated_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes
51605161
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
51615162
[`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute
51625163
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else

clippy_dev/src/update_lints.rs

+2
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,8 @@ fn gen_deprecated_lints_test(lints: &[DeprecatedLint]) -> String {
689689
fn gen_renamed_lints_test(lints: &[RenamedLint]) -> String {
690690
let mut seen_lints = HashSet::new();
691691
let mut res: String = GENERATED_FILE_COMMENT.into();
692+
693+
res.push_str("#![allow(clippy::duplicated_attributes)]\n");
692694
for lint in lints {
693695
if seen_lints.insert(&lint.new_name) {
694696
writeln!(res, "#![allow({})]", lint.new_name).unwrap();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use super::DUPLICATED_ATTRIBUTES;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use rustc_ast::{Attribute, MetaItem};
4+
use rustc_data_structures::fx::FxHashMap;
5+
use rustc_lint::EarlyContext;
6+
use rustc_span::{sym, Span};
7+
use std::collections::hash_map::Entry;
8+
9+
fn emit_if_duplicated(
10+
cx: &EarlyContext<'_>,
11+
attr: &MetaItem,
12+
attr_paths: &mut FxHashMap<String, Span>,
13+
complete_path: String,
14+
) {
15+
match attr_paths.entry(complete_path) {
16+
Entry::Vacant(v) => {
17+
v.insert(attr.span);
18+
},
19+
Entry::Occupied(o) => {
20+
span_lint_and_then(cx, DUPLICATED_ATTRIBUTES, attr.span, "duplicated attribute", |diag| {
21+
diag.span_note(*o.get(), "first defined here");
22+
diag.span_help(attr.span, "remove this attribute");
23+
});
24+
},
25+
}
26+
}
27+
28+
fn check_duplicated_attr(
29+
cx: &EarlyContext<'_>,
30+
attr: &MetaItem,
31+
attr_paths: &mut FxHashMap<String, Span>,
32+
parent: &mut Vec<String>,
33+
) {
34+
let Some(ident) = attr.ident() else { return };
35+
let name = ident.name;
36+
if name == sym::doc || name == sym::cfg_attr {
37+
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
38+
// conditions are the same.
39+
return;
40+
}
41+
if let Some(value) = attr.value_str() {
42+
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}={value}", parent.join(":")));
43+
} else if let Some(sub_attrs) = attr.meta_item_list() {
44+
parent.push(name.as_str().to_string());
45+
for sub_attr in sub_attrs {
46+
if let Some(meta) = sub_attr.meta_item() {
47+
check_duplicated_attr(cx, meta, attr_paths, parent);
48+
}
49+
}
50+
parent.pop();
51+
} else {
52+
emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.join(":")));
53+
}
54+
}
55+
56+
pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
57+
let mut attr_paths = FxHashMap::default();
58+
59+
for attr in attrs {
60+
if let Some(meta) = attr.meta() {
61+
check_duplicated_attr(cx, &meta, &mut attr_paths, &mut Vec::new());
62+
}
63+
}
64+
}

clippy_lints/src/attrs/mod.rs

+34-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod allow_attributes_without_reason;
44
mod blanket_clippy_restriction_lints;
55
mod deprecated_cfg_attr;
66
mod deprecated_semver;
7+
mod duplicated_attributes;
78
mod empty_line_after;
89
mod inline_always;
910
mod maybe_misused_cfg;
@@ -16,7 +17,7 @@ mod useless_attribute;
1617
mod utils;
1718

1819
use clippy_config::msrvs::Msrv;
19-
use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem};
20+
use rustc_ast::{Attribute, Crate, MetaItemKind, NestedMetaItem};
2021
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
2122
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
2223
use rustc_session::{declare_lint_pass, impl_lint_pass};
@@ -489,6 +490,32 @@ declare_clippy_lint! {
489490
"item has both inner and outer attributes"
490491
}
491492

493+
declare_clippy_lint! {
494+
/// ### What it does
495+
/// Checks for attributes that appear two or more times.
496+
///
497+
/// ### Why is this bad?
498+
/// Repeating an attribute on the same item (or globally on the same crate)
499+
/// is unnecessary and doesn't have an effect.
500+
///
501+
/// ### Example
502+
/// ```no_run
503+
/// #[allow(dead_code)]
504+
/// #[allow(dead_code)]
505+
/// fn foo() {}
506+
/// ```
507+
///
508+
/// Use instead:
509+
/// ```no_run
510+
/// #[allow(dead_code)]
511+
/// fn foo() {}
512+
/// ```
513+
#[clippy::version = "1.78.0"]
514+
pub DUPLICATED_ATTRIBUTES,
515+
suspicious,
516+
"duplicated attribute"
517+
}
518+
492519
declare_lint_pass!(Attributes => [
493520
ALLOW_ATTRIBUTES_WITHOUT_REASON,
494521
INLINE_ALWAYS,
@@ -568,12 +595,18 @@ impl_lint_pass!(EarlyAttributes => [
568595
DEPRECATED_CLIPPY_CFG_ATTR,
569596
UNNECESSARY_CLIPPY_CFG,
570597
MIXED_ATTRIBUTES_STYLE,
598+
DUPLICATED_ATTRIBUTES,
571599
]);
572600

573601
impl EarlyLintPass for EarlyAttributes {
602+
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
603+
duplicated_attributes::check(cx, &krate.attrs);
604+
}
605+
574606
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
575607
empty_line_after::check(cx, item);
576608
mixed_attributes_style::check(cx, item);
609+
duplicated_attributes::check(cx, &item.attrs);
577610
}
578611

579612
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
5454
crate::attrs::DEPRECATED_CFG_ATTR_INFO,
5555
crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO,
5656
crate::attrs::DEPRECATED_SEMVER_INFO,
57+
crate::attrs::DUPLICATED_ATTRIBUTES_INFO,
5758
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
5859
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
5960
crate::attrs::INLINE_ALWAYS_INFO,

tests/ui/allow_attributes_without_reason.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//@aux-build:proc_macros.rs
22
#![feature(lint_reasons)]
33
#![deny(clippy::allow_attributes_without_reason)]
4-
#![allow(unfulfilled_lint_expectations)]
4+
#![allow(unfulfilled_lint_expectations, clippy::duplicated_attributes)]
55

66
extern crate proc_macros;
77
use proc_macros::{external, with_span};

tests/ui/allow_attributes_without_reason.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: `allow` attribute without specifying a reason
22
--> tests/ui/allow_attributes_without_reason.rs:4:1
33
|
4-
LL | #![allow(unfulfilled_lint_expectations)]
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
LL | #![allow(unfulfilled_lint_expectations, clippy::duplicated_attributes)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
77
= help: try adding a reason at the end with `, reason = ".."`
88
note: the lint level is defined here

tests/ui/duplicated_attributes.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![warn(clippy::duplicated_attributes)]
2+
#![cfg(any(unix, windows))]
3+
#![allow(dead_code)]
4+
#![allow(dead_code)] //~ ERROR: duplicated attribute
5+
#![cfg(any(unix, windows))]
6+
//~^ ERROR: duplicated attribute
7+
//~| ERROR: duplicated attribute
8+
9+
#[cfg(any(unix, windows, target_os = "linux"))]
10+
#[allow(dead_code)]
11+
#[allow(dead_code)] //~ ERROR: duplicated attribute
12+
#[cfg(any(unix, windows, target_os = "linux"))]
13+
//~^ ERROR: duplicated attribute
14+
//~| ERROR: duplicated attribute
15+
fn foo() {}
16+
17+
fn main() {}

tests/ui/duplicated_attributes.stderr

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
error: duplicated attribute
2+
--> tests/ui/duplicated_attributes.rs:4:10
3+
|
4+
LL | #![allow(dead_code)]
5+
| ^^^^^^^^^
6+
|
7+
note: first defined here
8+
--> tests/ui/duplicated_attributes.rs:3:10
9+
|
10+
LL | #![allow(dead_code)]
11+
| ^^^^^^^^^
12+
help: remove this attribute
13+
--> tests/ui/duplicated_attributes.rs:4:10
14+
|
15+
LL | #![allow(dead_code)]
16+
| ^^^^^^^^^
17+
= note: `-D clippy::duplicated-attributes` implied by `-D warnings`
18+
= help: to override `-D warnings` add `#[allow(clippy::duplicated_attributes)]`
19+
20+
error: duplicated attribute
21+
--> tests/ui/duplicated_attributes.rs:5:12
22+
|
23+
LL | #![cfg(any(unix, windows))]
24+
| ^^^^
25+
|
26+
note: first defined here
27+
--> tests/ui/duplicated_attributes.rs:2:12
28+
|
29+
LL | #![cfg(any(unix, windows))]
30+
| ^^^^
31+
help: remove this attribute
32+
--> tests/ui/duplicated_attributes.rs:5:12
33+
|
34+
LL | #![cfg(any(unix, windows))]
35+
| ^^^^
36+
37+
error: duplicated attribute
38+
--> tests/ui/duplicated_attributes.rs:5:18
39+
|
40+
LL | #![cfg(any(unix, windows))]
41+
| ^^^^^^^
42+
|
43+
note: first defined here
44+
--> tests/ui/duplicated_attributes.rs:2:18
45+
|
46+
LL | #![cfg(any(unix, windows))]
47+
| ^^^^^^^
48+
help: remove this attribute
49+
--> tests/ui/duplicated_attributes.rs:5:18
50+
|
51+
LL | #![cfg(any(unix, windows))]
52+
| ^^^^^^^
53+
54+
error: duplicated attribute
55+
--> tests/ui/duplicated_attributes.rs:11:9
56+
|
57+
LL | #[allow(dead_code)]
58+
| ^^^^^^^^^
59+
|
60+
note: first defined here
61+
--> tests/ui/duplicated_attributes.rs:10:9
62+
|
63+
LL | #[allow(dead_code)]
64+
| ^^^^^^^^^
65+
help: remove this attribute
66+
--> tests/ui/duplicated_attributes.rs:11:9
67+
|
68+
LL | #[allow(dead_code)]
69+
| ^^^^^^^^^
70+
71+
error: duplicated attribute
72+
--> tests/ui/duplicated_attributes.rs:12:11
73+
|
74+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
75+
| ^^^^
76+
|
77+
note: first defined here
78+
--> tests/ui/duplicated_attributes.rs:9:11
79+
|
80+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
81+
| ^^^^
82+
help: remove this attribute
83+
--> tests/ui/duplicated_attributes.rs:12:11
84+
|
85+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
86+
| ^^^^
87+
88+
error: duplicated attribute
89+
--> tests/ui/duplicated_attributes.rs:12:17
90+
|
91+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
92+
| ^^^^^^^
93+
|
94+
note: first defined here
95+
--> tests/ui/duplicated_attributes.rs:9:17
96+
|
97+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
98+
| ^^^^^^^
99+
help: remove this attribute
100+
--> tests/ui/duplicated_attributes.rs:12:17
101+
|
102+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
103+
| ^^^^^^^
104+
105+
error: duplicated attribute
106+
--> tests/ui/duplicated_attributes.rs:12:26
107+
|
108+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
109+
| ^^^^^^^^^^^^^^^^^^^
110+
|
111+
note: first defined here
112+
--> tests/ui/duplicated_attributes.rs:9:26
113+
|
114+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
115+
| ^^^^^^^^^^^^^^^^^^^
116+
help: remove this attribute
117+
--> tests/ui/duplicated_attributes.rs:12:26
118+
|
119+
LL | #[cfg(any(unix, windows, target_os = "linux"))]
120+
| ^^^^^^^^^^^^^^^^^^^
121+
122+
error: aborting due to 7 previous errors
123+

tests/ui/empty_line_after_doc_comments.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//@aux-build:proc_macro_attr.rs
22
#![warn(clippy::empty_line_after_doc_comments)]
3-
#![allow(clippy::assertions_on_constants)]
3+
#![allow(clippy::assertions_on_constants, clippy::duplicated_attributes)]
44
#![feature(custom_inner_attributes)]
55
#![rustfmt::skip]
66

tests/ui/empty_line_after_outer_attribute.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//@aux-build:proc_macro_attr.rs
22
#![warn(clippy::empty_line_after_outer_attr)]
3-
#![allow(clippy::assertions_on_constants)]
3+
#![allow(clippy::assertions_on_constants, clippy::duplicated_attributes)]
44
#![feature(custom_inner_attributes)]
55
#![rustfmt::skip]
66

tests/ui/mixed_attributes_style.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::mixed_attributes_style)]
2+
#![allow(clippy::duplicated_attributes)]
23

34
#[allow(unused)] //~ ERROR: item has both inner and outer attributes
45
fn foo1() {

tests/ui/mixed_attributes_style.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: item has both inner and outer attributes
2-
--> tests/ui/mixed_attributes_style.rs:3:1
2+
--> tests/ui/mixed_attributes_style.rs:4:1
33
|
44
LL | / #[allow(unused)]
55
LL | | fn foo1() {
@@ -10,7 +10,7 @@ LL | | #![allow(unused)]
1010
= help: to override `-D warnings` add `#[allow(clippy::mixed_attributes_style)]`
1111

1212
error: item has both inner and outer attributes
13-
--> tests/ui/mixed_attributes_style.rs:17:1
13+
--> tests/ui/mixed_attributes_style.rs:18:1
1414
|
1515
LL | / /// linux
1616
LL | |
@@ -19,7 +19,7 @@ LL | | //! windows
1919
| |_______________^
2020

2121
error: item has both inner and outer attributes
22-
--> tests/ui/mixed_attributes_style.rs:32:1
22+
--> tests/ui/mixed_attributes_style.rs:33:1
2323
|
2424
LL | / #[allow(unused)]
2525
LL | | mod bar {

tests/ui/rename.fixed

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use that command to update this file and do not edit by hand.
33
// Manual edits will be overwritten.
44

5+
#![allow(clippy::duplicated_attributes)]
56
#![allow(clippy::almost_complete_range)]
67
#![allow(clippy::disallowed_names)]
78
#![allow(clippy::blocks_in_conditions)]

tests/ui/rename.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use that command to update this file and do not edit by hand.
33
// Manual edits will be overwritten.
44

5+
#![allow(clippy::duplicated_attributes)]
56
#![allow(clippy::almost_complete_range)]
67
#![allow(clippy::disallowed_names)]
78
#![allow(clippy::blocks_in_conditions)]

0 commit comments

Comments
 (0)