Skip to content

Commit 787fef0

Browse files
committed
Rework empty_with_brackets:
* Merge code paths for struct and variants * Adjust the span to not include leading whitespace * Don't lint in macros * Add proc macro detection * Don't lint if there're comments inside the brackets.
1 parent f2c74e2 commit 787fef0

7 files changed

+274
-151
lines changed
Lines changed: 64 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::source::snippet_opt;
2+
use clippy_utils::source::{IntoSpan, SpanRangeExt};
33
use rustc_ast::ast::{Item, ItemKind, Variant, VariantData};
44
use rustc_errors::Applicability;
55
use rustc_lexer::TokenKind;
6-
use rustc_lint::{EarlyContext, EarlyLintPass};
6+
use rustc_lint::{EarlyContext, EarlyLintPass, Lint};
77
use rustc_session::declare_lint_pass;
88
use rustc_span::Span;
99

@@ -74,93 +74,78 @@ declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM
7474

7575
impl EarlyLintPass for EmptyWithBrackets {
7676
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
77-
let span_after_ident = item.span.with_lo(item.ident.span.hi());
78-
79-
if let ItemKind::Struct(var_data, _) = &item.kind
80-
&& has_brackets(var_data)
81-
&& has_no_fields(cx, var_data, span_after_ident)
82-
{
83-
span_lint_and_then(
77+
if let ItemKind::Struct(var_data, _) = &item.kind {
78+
check(
8479
cx,
80+
var_data,
81+
item.span,
82+
item.ident.span,
8583
EMPTY_STRUCTS_WITH_BRACKETS,
86-
span_after_ident,
87-
"found empty brackets on struct declaration",
88-
|diagnostic| {
89-
diagnostic.span_suggestion_hidden(
90-
span_after_ident,
91-
"remove the brackets",
92-
";",
93-
Applicability::Unspecified,
94-
);
95-
},
84+
"non-unit struct contains no fields",
85+
true,
9686
);
9787
}
9888
}
9989

10090
fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) {
101-
let span_after_ident = variant.span.with_lo(variant.ident.span.hi());
102-
103-
if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) {
104-
span_lint_and_then(
105-
cx,
106-
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
107-
span_after_ident,
108-
"enum variant has empty brackets",
109-
|diagnostic| {
110-
diagnostic.span_suggestion_hidden(
111-
span_after_ident,
112-
"remove the brackets",
113-
"",
114-
Applicability::MaybeIncorrect,
115-
);
116-
},
117-
);
118-
}
91+
check(
92+
cx,
93+
&variant.data,
94+
variant.span,
95+
variant.ident.span,
96+
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
97+
"non-unit variant contains no fields",
98+
false,
99+
);
119100
}
120101
}
121102

122-
fn has_no_ident_token(braces_span_str: &str) -> bool {
123-
!rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
124-
}
125-
126-
fn has_brackets(var_data: &VariantData) -> bool {
127-
!matches!(var_data, VariantData::Unit(_))
128-
}
129-
130-
fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
131-
if !var_data.fields().is_empty() {
132-
return false;
133-
}
134-
135-
// there might still be field declarations hidden from the AST
136-
// (conditionally compiled code using #[cfg(..)])
137-
138-
let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
139-
return false;
103+
fn check(
104+
cx: &EarlyContext<'_>,
105+
data: &VariantData,
106+
item_sp: Span,
107+
name_sp: Span,
108+
lint: &'static Lint,
109+
msg: &'static str,
110+
needs_semi: bool,
111+
) {
112+
let (fields, has_semi, start_char, end_char, help_msg) = match &data {
113+
VariantData::Struct { fields, .. } => (fields, false, '{', '}', "remove the braces"),
114+
VariantData::Tuple(fields, _) => (fields, needs_semi, '(', ')', "remove the parentheses"),
115+
VariantData::Unit(_) => return,
140116
};
141-
142-
has_no_ident_token(braces_span_str.as_ref())
143-
}
144-
145-
#[cfg(test)]
146-
mod unit_test {
147-
use super::*;
148-
149-
#[test]
150-
fn test_has_no_ident_token() {
151-
let input = "{ field: u8 }";
152-
assert!(!has_no_ident_token(input));
153-
154-
let input = "(u8, String);";
155-
assert!(!has_no_ident_token(input));
156-
157-
let input = " {
158-
// test = 5
159-
}
160-
";
161-
assert!(has_no_ident_token(input));
162-
163-
let input = " ();";
164-
assert!(has_no_ident_token(input));
117+
if fields.is_empty()
118+
&& !item_sp.from_expansion()
119+
&& !name_sp.from_expansion()
120+
&& let name_hi = name_sp.hi()
121+
&& let Some(err_range) = (name_hi..item_sp.hi()).clone().map_range(cx, |src, range| {
122+
let src = src.get(range.clone())?;
123+
let (src, end) = if has_semi {
124+
(src.strip_suffix(';')?, range.end - 1)
125+
} else {
126+
(src, range.end)
127+
};
128+
let trimmed = src.trim_start();
129+
let start = range.start + (src.len() - trimmed.len());
130+
// Proc-macro check.
131+
let trimmed = trimmed.strip_prefix(start_char)?.strip_suffix(end_char)?;
132+
// Check for anything inside the brackets, including comments.
133+
rustc_lexer::tokenize(trimmed)
134+
.all(|tt| matches!(tt.kind, TokenKind::Whitespace))
135+
.then_some(start..end)
136+
})
137+
{
138+
span_lint_and_then(cx, lint, err_range.clone().into_span(), msg, |diagnostic| {
139+
diagnostic.span_suggestion_hidden(
140+
(name_hi..err_range.end).into_span(),
141+
help_msg,
142+
if has_semi || !needs_semi {
143+
String::new()
144+
} else {
145+
";".into()
146+
},
147+
Applicability::MaybeIncorrect,
148+
);
149+
});
165150
}
166151
}
Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,61 @@
1-
#![warn(clippy::empty_enum_variants_with_brackets)]
1+
//@aux-build:proc_macros.rs
2+
#![deny(clippy::empty_enum_variants_with_brackets)]
23
#![allow(dead_code)]
34

5+
extern crate proc_macros;
6+
use proc_macros::{external, with_span};
7+
48
pub enum PublicTestEnum {
5-
NonEmptyBraces { x: i32, y: i32 }, // No error
6-
NonEmptyParentheses(i32, i32), // No error
7-
EmptyBraces, //~ ERROR: enum variant has empty brackets
8-
EmptyParentheses, //~ ERROR: enum variant has empty brackets
9+
NonEmptyBraces { x: i32, y: i32 },
10+
NonEmptyParentheses(i32, i32),
11+
EmptyBraces, //~ empty_enum_variants_with_brackets
12+
EmptyParentheses, //~ empty_enum_variants_with_brackets
913
}
1014

1115
enum TestEnum {
12-
NonEmptyBraces { x: i32, y: i32 }, // No error
13-
NonEmptyParentheses(i32, i32), // No error
14-
EmptyBraces, //~ ERROR: enum variant has empty brackets
15-
EmptyParentheses, //~ ERROR: enum variant has empty brackets
16-
AnotherEnum, // No error
16+
NonEmptyBraces {
17+
x: i32,
18+
y: i32,
19+
},
20+
NonEmptyParentheses(i32, i32),
21+
EmptyBraces, //~ empty_enum_variants_with_brackets
22+
EmptyParentheses, //~ empty_enum_variants_with_brackets
23+
AnotherEnum,
24+
#[rustfmt::skip]
25+
WithWhitespace, //~ empty_enum_variants_with_brackets
26+
WithComment {
27+
// Some long explanation here
28+
},
1729
}
1830

1931
enum TestEnumWithFeatures {
2032
NonEmptyBraces {
2133
#[cfg(feature = "thisisneverenabled")]
2234
x: i32,
23-
}, // No error
24-
NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32), // No error
35+
},
36+
NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32),
37+
}
38+
39+
external! {
40+
enum External {
41+
Foo {},
42+
}
43+
}
44+
45+
with_span! {
46+
span
47+
enum ProcMacro {
48+
Foo(),
49+
}
50+
}
51+
52+
macro_rules! m {
53+
($($ty:ty),*) => {
54+
enum Macro {
55+
Foo($($ty),*),
56+
}
57+
}
2558
}
59+
m! {}
2660

2761
fn main() {}
Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,61 @@
1-
#![warn(clippy::empty_enum_variants_with_brackets)]
1+
//@aux-build:proc_macros.rs
2+
#![deny(clippy::empty_enum_variants_with_brackets)]
23
#![allow(dead_code)]
34

5+
extern crate proc_macros;
6+
use proc_macros::{external, with_span};
7+
48
pub enum PublicTestEnum {
5-
NonEmptyBraces { x: i32, y: i32 }, // No error
6-
NonEmptyParentheses(i32, i32), // No error
7-
EmptyBraces {}, //~ ERROR: enum variant has empty brackets
8-
EmptyParentheses(), //~ ERROR: enum variant has empty brackets
9+
NonEmptyBraces { x: i32, y: i32 },
10+
NonEmptyParentheses(i32, i32),
11+
EmptyBraces {}, //~ empty_enum_variants_with_brackets
12+
EmptyParentheses(), //~ empty_enum_variants_with_brackets
913
}
1014

1115
enum TestEnum {
12-
NonEmptyBraces { x: i32, y: i32 }, // No error
13-
NonEmptyParentheses(i32, i32), // No error
14-
EmptyBraces {}, //~ ERROR: enum variant has empty brackets
15-
EmptyParentheses(), //~ ERROR: enum variant has empty brackets
16-
AnotherEnum, // No error
16+
NonEmptyBraces {
17+
x: i32,
18+
y: i32,
19+
},
20+
NonEmptyParentheses(i32, i32),
21+
EmptyBraces {}, //~ empty_enum_variants_with_brackets
22+
EmptyParentheses(), //~ empty_enum_variants_with_brackets
23+
AnotherEnum,
24+
#[rustfmt::skip]
25+
WithWhitespace { }, //~ empty_enum_variants_with_brackets
26+
WithComment {
27+
// Some long explanation here
28+
},
1729
}
1830

1931
enum TestEnumWithFeatures {
2032
NonEmptyBraces {
2133
#[cfg(feature = "thisisneverenabled")]
2234
x: i32,
23-
}, // No error
24-
NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32), // No error
35+
},
36+
NonEmptyParentheses(#[cfg(feature = "thisisneverenabled")] i32),
37+
}
38+
39+
external! {
40+
enum External {
41+
Foo {},
42+
}
43+
}
44+
45+
with_span! {
46+
span
47+
enum ProcMacro {
48+
Foo(),
49+
}
50+
}
51+
52+
macro_rules! m {
53+
($($ty:ty),*) => {
54+
enum Macro {
55+
Foo($($ty),*),
56+
}
57+
}
2558
}
59+
m! {}
2660

2761
fn main() {}
Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,47 @@
1-
error: enum variant has empty brackets
2-
--> tests/ui/empty_enum_variants_with_brackets.rs:7:16
1+
error: non-unit variant contains no fields
2+
--> tests/ui/empty_enum_variants_with_brackets.rs:11:17
33
|
44
LL | EmptyBraces {},
5-
| ^^^
5+
| ^^
66
|
7-
= note: `-D clippy::empty-enum-variants-with-brackets` implied by `-D warnings`
8-
= help: to override `-D warnings` add `#[allow(clippy::empty_enum_variants_with_brackets)]`
9-
= help: remove the brackets
7+
note: the lint level is defined here
8+
--> tests/ui/empty_enum_variants_with_brackets.rs:2:9
9+
|
10+
LL | #![deny(clippy::empty_enum_variants_with_brackets)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= help: remove the braces
1013

11-
error: enum variant has empty brackets
12-
--> tests/ui/empty_enum_variants_with_brackets.rs:8:21
14+
error: non-unit variant contains no fields
15+
--> tests/ui/empty_enum_variants_with_brackets.rs:12:21
1316
|
1417
LL | EmptyParentheses(),
1518
| ^^
1619
|
17-
= help: remove the brackets
20+
= help: remove the parentheses
1821

19-
error: enum variant has empty brackets
20-
--> tests/ui/empty_enum_variants_with_brackets.rs:14:16
22+
error: non-unit variant contains no fields
23+
--> tests/ui/empty_enum_variants_with_brackets.rs:21:17
2124
|
2225
LL | EmptyBraces {},
23-
| ^^^
26+
| ^^
2427
|
25-
= help: remove the brackets
28+
= help: remove the braces
2629

27-
error: enum variant has empty brackets
28-
--> tests/ui/empty_enum_variants_with_brackets.rs:15:21
30+
error: non-unit variant contains no fields
31+
--> tests/ui/empty_enum_variants_with_brackets.rs:22:21
2932
|
3033
LL | EmptyParentheses(),
3134
| ^^
3235
|
33-
= help: remove the brackets
36+
= help: remove the parentheses
37+
38+
error: non-unit variant contains no fields
39+
--> tests/ui/empty_enum_variants_with_brackets.rs:25:20
40+
|
41+
LL | WithWhitespace { },
42+
| ^^^^
43+
|
44+
= help: remove the braces
3445

35-
error: aborting due to 4 previous errors
46+
error: aborting due to 5 previous errors
3647

0 commit comments

Comments
 (0)