Skip to content

Commit c0d8961

Browse files
committed
add lint for struct field names
side effect for `enum_variants`: use .first() instead of .get(0) in enum_variants lint move to_camel_case to str_util module move module, enum and struct name repetitions check to a single file `item_name_repetitions` rename enum_variants threshold config option
1 parent f3cf5e6 commit c0d8961

38 files changed

+1076
-238
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5371,6 +5371,7 @@ Released 2018-09-13
53715371
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
53725372
[`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings
53735373
[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
5374+
[`struct_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_field_names
53745375
[`stutter`]: https://rust-lang.github.io/rust-clippy/master/index.html#stutter
53755376
[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops
53765377
[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl

clippy_lints/src/declared_lints.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
154154
crate::endian_bytes::LITTLE_ENDIAN_BYTES_INFO,
155155
crate::entry::MAP_ENTRY_INFO,
156156
crate::enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT_INFO,
157-
crate::enum_variants::ENUM_VARIANT_NAMES_INFO,
158-
crate::enum_variants::MODULE_INCEPTION_INFO,
159-
crate::enum_variants::MODULE_NAME_REPETITIONS_INFO,
160157
crate::equatable_if_let::EQUATABLE_IF_LET_INFO,
161158
crate::error_impl_error::ERROR_IMPL_ERROR_INFO,
162159
crate::escape::BOXED_LOCAL_INFO,
@@ -226,6 +223,10 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
226223
crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO,
227224
crate::int_plus_one::INT_PLUS_ONE_INFO,
228225
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
226+
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
227+
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
228+
crate::item_name_repetitions::MODULE_NAME_REPETITIONS_INFO,
229+
crate::item_name_repetitions::STRUCT_FIELD_NAMES_INFO,
229230
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
230231
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
231232
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,

clippy_lints/src/functions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ declare_clippy_lint! {
360360
}
361361

362362
#[derive(Copy, Clone)]
363+
#[allow(clippy::struct_field_names)]
363364
pub struct Functions {
364365
too_many_arguments_threshold: u64,
365366
too_many_lines_threshold: u64,

clippy_lints/src/enum_variants.rs renamed to clippy_lints/src/item_name_repetitions.rs

Lines changed: 171 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! lint on enum variants that are prefixed or suffixed by the same characters
22
33
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir};
4+
use clippy_utils::macros::span_is_local;
45
use clippy_utils::source::is_present_in_source;
5-
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start};
6-
use rustc_hir::{EnumDef, Item, ItemKind, OwnerId, Variant};
6+
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
7+
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
78
use rustc_lint::{LateContext, LateLintPass};
89
use rustc_session::{declare_tool_lint, impl_lint_pass};
910
use rustc_span::source_map::Span;
@@ -103,32 +104,184 @@ declare_clippy_lint! {
103104
style,
104105
"modules that have the same name as their parent module"
105106
}
107+
declare_clippy_lint! {
108+
/// ### What it does
109+
/// Detects struct fields that are prefixed or suffixed
110+
/// by the same characters or the name of the struct itself.
111+
///
112+
/// ### Why is this bad?
113+
/// Information common to all struct fields is better represented in the struct name.
114+
///
115+
/// ### Limitations
116+
/// Characters with no casing will be considered when comparing prefixes/suffixes
117+
/// This applies to numbers and non-ascii characters without casing
118+
/// e.g. `foo1` and `foo2` is considered to have different prefixes
119+
/// (the prefixes are `foo1` and `foo2` respectively), as also `bar螃`, `bar蟹`
120+
///
121+
/// ### Example
122+
/// ```rust
123+
/// struct Cake {
124+
/// cake_sugar: u8,
125+
/// cake_flour: u8,
126+
/// cake_eggs: u8
127+
/// }
128+
/// ```
129+
/// Use instead:
130+
/// ```rust
131+
/// struct Cake {
132+
/// sugar: u8,
133+
/// flour: u8,
134+
/// eggs: u8
135+
/// }
136+
/// ```
137+
#[clippy::version = "1.75.0"]
138+
pub STRUCT_FIELD_NAMES,
139+
pedantic,
140+
"structs where all fields share a prefix/postfix or contain the name of the struct"
141+
}
106142

107-
pub struct EnumVariantNames {
143+
pub struct ItemNameRepetitions {
108144
modules: Vec<(Symbol, String, OwnerId)>,
109-
threshold: u64,
145+
enum_threshold: u64,
146+
struct_threshold: u64,
110147
avoid_breaking_exported_api: bool,
111148
allow_private_module_inception: bool,
112149
}
113150

114-
impl EnumVariantNames {
151+
impl ItemNameRepetitions {
115152
#[must_use]
116-
pub fn new(threshold: u64, avoid_breaking_exported_api: bool, allow_private_module_inception: bool) -> Self {
153+
pub fn new(
154+
enum_threshold: u64,
155+
struct_threshold: u64,
156+
avoid_breaking_exported_api: bool,
157+
allow_private_module_inception: bool,
158+
) -> Self {
117159
Self {
118160
modules: Vec::new(),
119-
threshold,
161+
enum_threshold,
162+
struct_threshold,
120163
avoid_breaking_exported_api,
121164
allow_private_module_inception,
122165
}
123166
}
124167
}
125168

126-
impl_lint_pass!(EnumVariantNames => [
169+
impl_lint_pass!(ItemNameRepetitions => [
127170
ENUM_VARIANT_NAMES,
171+
STRUCT_FIELD_NAMES,
128172
MODULE_NAME_REPETITIONS,
129173
MODULE_INCEPTION
130174
]);
131175

176+
#[must_use]
177+
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
178+
prefixes.iter().all(|p| p == &"" || p == &"_")
179+
}
180+
181+
fn check_fields(cx: &LateContext<'_>, threshold: u64, item: &Item<'_>, fields: &[FieldDef<'_>]) {
182+
if (fields.len() as u64) < threshold {
183+
return;
184+
}
185+
186+
check_struct_name_repetition(cx, item, fields);
187+
188+
// if the SyntaxContext of the identifiers of the fields and struct differ dont lint them.
189+
// this prevents linting in macros in which the location of the field identifier names differ
190+
if !fields.iter().all(|field| item.ident.span.eq_ctxt(field.ident.span)) {
191+
return;
192+
}
193+
194+
let mut pre: Vec<&str> = match fields.first() {
195+
Some(first_field) => first_field.ident.name.as_str().split('_').collect(),
196+
None => return,
197+
};
198+
let mut post = pre.clone();
199+
post.reverse();
200+
for field in fields {
201+
let field_split: Vec<&str> = field.ident.name.as_str().split('_').collect();
202+
if field_split.len() == 1 {
203+
return;
204+
}
205+
206+
pre = pre
207+
.into_iter()
208+
.zip(field_split.iter())
209+
.take_while(|(a, b)| &a == b)
210+
.map(|e| e.0)
211+
.collect();
212+
post = post
213+
.into_iter()
214+
.zip(field_split.iter().rev())
215+
.take_while(|(a, b)| &a == b)
216+
.map(|e| e.0)
217+
.collect();
218+
}
219+
let prefix = pre.join("_");
220+
post.reverse();
221+
let postfix = match post.last() {
222+
Some(&"") => post.join("_") + "_",
223+
Some(_) | None => post.join("_"),
224+
};
225+
if fields.len() > 1 {
226+
let (what, value) = match (
227+
prefix.is_empty() || prefix.chars().all(|c| c == '_'),
228+
postfix.is_empty(),
229+
) {
230+
(true, true) => return,
231+
(false, _) => ("pre", prefix),
232+
(true, false) => ("post", postfix),
233+
};
234+
span_lint_and_help(
235+
cx,
236+
STRUCT_FIELD_NAMES,
237+
item.span,
238+
&format!("all fields have the same {what}fix: `{value}`"),
239+
None,
240+
&format!("remove the {what}fixes"),
241+
);
242+
}
243+
}
244+
245+
fn check_struct_name_repetition(cx: &LateContext<'_>, item: &Item<'_>, fields: &[FieldDef<'_>]) {
246+
let snake_name = to_snake_case(item.ident.name.as_str());
247+
let item_name_words: Vec<&str> = snake_name.split('_').collect();
248+
for field in fields {
249+
if field.ident.span.eq_ctxt(item.ident.span) {
250+
//consider linting only if the field identifier has the same SyntaxContext as the item(struct)
251+
let field_words: Vec<&str> = field.ident.name.as_str().split('_').collect();
252+
if field_words.len() >= item_name_words.len() {
253+
// if the field name is shorter than the struct name it cannot contain it
254+
if field_words.iter().zip(item_name_words.iter()).all(|(a, b)| a == b) {
255+
span_lint_hir(
256+
cx,
257+
STRUCT_FIELD_NAMES,
258+
field.hir_id,
259+
field.span,
260+
"field name starts with the struct's name",
261+
);
262+
}
263+
if field_words.len() > item_name_words.len() {
264+
// lint only if the end is not covered by the start
265+
if field_words
266+
.iter()
267+
.rev()
268+
.zip(item_name_words.iter().rev())
269+
.all(|(a, b)| a == b)
270+
{
271+
span_lint_hir(
272+
cx,
273+
STRUCT_FIELD_NAMES,
274+
field.hir_id,
275+
field.span,
276+
"field name ends with the struct's name",
277+
);
278+
}
279+
}
280+
}
281+
}
282+
}
283+
}
284+
132285
fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) {
133286
let name = variant.ident.name.as_str();
134287
let item_name_chars = item_name.chars().count();
@@ -218,35 +371,7 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
218371
);
219372
}
220373

221-
#[must_use]
222-
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
223-
prefixes.iter().all(|p| p == &"" || p == &"_")
224-
}
225-
226-
#[must_use]
227-
fn to_camel_case(item_name: &str) -> String {
228-
let mut s = String::new();
229-
let mut up = true;
230-
for c in item_name.chars() {
231-
if c.is_uppercase() {
232-
// we only turn snake case text into CamelCase
233-
return item_name.to_string();
234-
}
235-
if c == '_' {
236-
up = true;
237-
continue;
238-
}
239-
if up {
240-
up = false;
241-
s.extend(c.to_uppercase());
242-
} else {
243-
s.push(c);
244-
}
245-
}
246-
s
247-
}
248-
249-
impl LateLintPass<'_> for EnumVariantNames {
374+
impl LateLintPass<'_> for ItemNameRepetitions {
250375
fn check_item_post(&mut self, _cx: &LateContext<'_>, _item: &Item<'_>) {
251376
let last = self.modules.pop();
252377
assert!(last.is_some());
@@ -303,9 +428,15 @@ impl LateLintPass<'_> for EnumVariantNames {
303428
}
304429
}
305430
}
306-
if let ItemKind::Enum(ref def, _) = item.kind {
307-
if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id)) {
308-
check_variant(cx, self.threshold, def, item_name, item.span);
431+
if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id))
432+
&& span_is_local(item.span)
433+
{
434+
match item.kind {
435+
ItemKind::Enum(def, _) => check_variant(cx, self.enum_threshold, &def, item_name, item.span),
436+
ItemKind::Struct(VariantData::Struct(fields, _), _) => {
437+
check_fields(cx, self.struct_threshold, item, fields);
438+
},
439+
_ => (),
309440
}
310441
}
311442
self.modules.push((item.ident.name, item_camel, item.owner_id));

clippy_lints/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ mod empty_structs_with_brackets;
121121
mod endian_bytes;
122122
mod entry;
123123
mod enum_clike;
124-
mod enum_variants;
125124
mod equatable_if_let;
126125
mod error_impl_error;
127126
mod escape;
@@ -166,6 +165,7 @@ mod inline_fn_without_body;
166165
mod instant_subtraction;
167166
mod int_plus_one;
168167
mod invalid_upcast_comparisons;
168+
mod item_name_repetitions;
169169
mod items_after_statements;
170170
mod items_after_test_module;
171171
mod iter_not_returning_iterator;
@@ -850,10 +850,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
850850
))
851851
});
852852
let enum_variant_name_threshold = conf.enum_variant_name_threshold;
853+
let struct_field_name_threshold = conf.struct_field_name_threshold;
853854
let allow_private_module_inception = conf.allow_private_module_inception;
854855
store.register_late_pass(move |_| {
855-
Box::new(enum_variants::EnumVariantNames::new(
856+
Box::new(item_name_repetitions::ItemNameRepetitions::new(
856857
enum_variant_name_threshold,
858+
struct_field_name_threshold,
857859
avoid_breaking_exported_api,
858860
allow_private_module_inception,
859861
))

clippy_lints/src/only_used_in_recursion.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ impl Usage {
134134
/// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the
135135
/// `DefId` of the function paired with the parameter's index.
136136
#[derive(Default)]
137+
#[allow(clippy::struct_field_names)]
137138
struct Params {
138139
params: Vec<Param>,
139140
by_id: HirIdMap<usize>,

clippy_lints/src/types/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ impl Types {
578578
}
579579
}
580580

581-
#[allow(clippy::struct_excessive_bools)]
581+
#[allow(clippy::struct_excessive_bools, clippy::struct_field_names)]
582582
#[derive(Clone, Copy, Default)]
583583
struct CheckTyContext {
584584
is_in_trait_impl: bool,

clippy_lints/src/utils/conf.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ define_Conf! {
360360
///
361361
/// The minimum number of enum variants for the lints about variant names to trigger
362362
(enum_variant_name_threshold: u64 = 3),
363+
/// Lint: STRUCT_VARIANT_NAMES.
364+
///
365+
/// The minimum number of struct fields for the lints about field names to trigger
366+
(struct_field_name_threshold: u64 = 3),
363367
/// Lint: LARGE_ENUM_VARIANT.
364368
///
365369
/// The maximum size of an enum's variant to avoid box suggestion

0 commit comments

Comments
 (0)