Skip to content

Commit e55c516

Browse files
committed
Rework module_inception
* Don't check for repetition if the previous module crosses a body boundary. * Don't take a snippet of the entire item. * Check each item's visibility once. * Use `is_from_proc_macro` before linting
1 parent da7b678 commit e55c516

File tree

2 files changed

+127
-73
lines changed

2 files changed

+127
-73
lines changed

clippy_lints/src/item_name_repetitions.rs

Lines changed: 118 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir};
3-
use clippy_utils::is_bool;
4-
use clippy_utils::macros::span_is_local;
5-
use clippy_utils::source::is_present_in_source;
63
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
4+
use clippy_utils::{is_bool, is_from_proc_macro};
75
use rustc_data_structures::fx::FxHashSet;
8-
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, QPath, TyKind, Variant, VariantData};
6+
use rustc_hir::{Body, EnumDef, FieldDef, Item, ItemKind, QPath, TyKind, UseKind, Variant, VariantData};
97
use rustc_lint::{LateContext, LateLintPass};
108
use rustc_session::impl_lint_pass;
119
use rustc_span::symbol::Symbol;
@@ -158,7 +156,8 @@ declare_clippy_lint! {
158156
}
159157

160158
pub struct ItemNameRepetitions {
161-
modules: Vec<(Symbol, String, OwnerId)>,
159+
/// The module path the lint pass is in.
160+
modules: Vec<ModInfo>,
162161
enum_threshold: u64,
163162
struct_threshold: u64,
164163
avoid_breaking_exported_api: bool,
@@ -167,6 +166,17 @@ pub struct ItemNameRepetitions {
167166
allowed_prefixes: FxHashSet<String>,
168167
}
169168

169+
struct ModInfo {
170+
name: Symbol,
171+
name_camel: String,
172+
/// Does this module have the `pub` visibility modifier.
173+
is_public: bool,
174+
/// How many bodies are between this module and the current lint pass position.
175+
///
176+
/// Only the most recently seen module is updated when entering/exiting a body.
177+
in_body_count: u32,
178+
}
179+
170180
impl ItemNameRepetitions {
171181
pub fn new(conf: &'static Conf) -> Self {
172182
Self {
@@ -458,92 +468,127 @@ fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_>
458468
}
459469

460470
impl LateLintPass<'_> for ItemNameRepetitions {
461-
fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) {
462-
let Some(_ident) = item.kind.ident() else { return };
463-
464-
let last = self.modules.pop();
465-
assert!(last.is_some());
471+
fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
472+
if matches!(item.kind, ItemKind::Mod(..)) {
473+
let prev = self.modules.pop();
474+
debug_assert!(prev.is_some());
475+
}
466476
}
467477

468-
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
469-
let Some(ident) = item.kind.ident() else { return };
470-
471-
let item_name = ident.name.as_str();
472-
let item_camel = to_camel_case(item_name);
473-
if !item.span.from_expansion() && is_present_in_source(cx, item.span)
474-
&& let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules
475-
// constants don't have surrounding modules
476-
&& !mod_camel.is_empty()
477-
{
478-
if mod_name == &ident.name
479-
&& let ItemKind::Mod(..) = item.kind
480-
&& (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public())
481-
{
482-
span_lint(
483-
cx,
484-
MODULE_INCEPTION,
485-
item.span,
486-
"module has the same name as its containing module",
487-
);
488-
}
478+
fn check_body(&mut self, _: &LateContext<'_>, _: &Body<'_>) {
479+
if let [.., last] = &mut *self.modules {
480+
last.in_body_count += 1;
481+
}
482+
}
489483

490-
// The `module_name_repetitions` lint should only trigger if the item has the module in its
491-
// name. Having the same name is only accepted if `allow_exact_repetition` is set to `true`.
484+
fn check_body_post(&mut self, _: &LateContext<'_>, _: &Body<'_>) {
485+
if let [.., last] = &mut *self.modules {
486+
last.in_body_count -= 1;
487+
}
488+
}
492489

493-
let both_are_public =
494-
cx.tcx.visibility(item.owner_id).is_public() && cx.tcx.visibility(mod_owner_id.def_id).is_public();
490+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
491+
let ident = match item.kind {
492+
ItemKind::Mod(ident, _) => {
493+
if let [.., prev] = &*self.modules
494+
&& prev.name == ident.name
495+
&& prev.in_body_count == 0
496+
&& (!self.allow_private_module_inception || prev.is_public)
497+
&& !item.span.from_expansion()
498+
&& !is_from_proc_macro(cx, item)
499+
{
500+
span_lint(
501+
cx,
502+
MODULE_INCEPTION,
503+
item.span,
504+
"module has the same name as its containing module",
505+
);
506+
}
507+
ident
508+
},
495509

496-
if both_are_public && !self.allow_exact_repetitions && item_camel == *mod_camel {
497-
span_lint(
498-
cx,
499-
MODULE_NAME_REPETITIONS,
500-
ident.span,
501-
"item name is the same as its containing module's name",
502-
);
503-
}
510+
ItemKind::Enum(ident, def, _) => {
511+
if !ident.span.in_external_macro(cx.tcx.sess.source_map()) {
512+
self.check_variants(cx, item, &def);
513+
}
514+
ident
515+
},
516+
ItemKind::Struct(ident, data, _) => {
517+
if let VariantData::Struct { fields, .. } = data
518+
&& !ident.span.in_external_macro(cx.tcx.sess.source_map())
519+
{
520+
self.check_fields(cx, item, fields);
521+
}
522+
ident
523+
},
504524

505-
if both_are_public && item_camel.len() > mod_camel.len() {
506-
let matching = count_match_start(mod_camel, &item_camel);
507-
let rmatching = count_match_end(mod_camel, &item_camel);
508-
let nchars = mod_camel.chars().count();
525+
ItemKind::Const(ident, ..)
526+
| ItemKind::ExternCrate(_, ident)
527+
| ItemKind::Fn { ident, .. }
528+
| ItemKind::Macro(ident, ..)
529+
| ItemKind::Static(ident, ..)
530+
| ItemKind::Trait(_, _, ident, ..)
531+
| ItemKind::TraitAlias(ident, ..)
532+
| ItemKind::TyAlias(ident, ..)
533+
| ItemKind::Union(ident, ..)
534+
| ItemKind::Use(_, UseKind::Single(ident)) => ident,
535+
536+
ItemKind::ForeignMod { .. } | ItemKind::GlobalAsm { .. } | ItemKind::Impl(_) | ItemKind::Use(..) => return,
537+
};
509538

510-
let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric();
539+
let item_name = ident.name.as_str();
540+
let item_camel = to_camel_case(item_name);
511541

512-
if matching.char_count == nchars {
513-
match item_camel.chars().nth(nchars) {
514-
Some(c) if is_word_beginning(c) => span_lint(
542+
if let [.., prev] = &*self.modules
543+
&& prev.is_public
544+
&& prev.in_body_count == 0
545+
&& !item.span.from_expansion()
546+
&& cx.tcx.visibility(item.owner_id).is_public()
547+
{
548+
if !self.allow_exact_repetitions && item_camel == prev.name_camel {
549+
if !is_from_proc_macro(cx, item) {
550+
span_lint(
551+
cx,
552+
MODULE_NAME_REPETITIONS,
553+
ident.span,
554+
"item name is the same as its containing module's name",
555+
);
556+
}
557+
} else if item_camel.len() > prev.name_camel.len() {
558+
if let Some(s) = item_camel.strip_prefix(&prev.name_camel)
559+
&& let Some(c) = s.chars().next()
560+
&& (c == '_' || c.is_uppercase() || c.is_numeric())
561+
{
562+
if !is_from_proc_macro(cx, item) {
563+
span_lint(
515564
cx,
516565
MODULE_NAME_REPETITIONS,
517566
ident.span,
518567
"item name starts with its containing module's name",
519-
),
520-
_ => (),
568+
);
521569
}
522-
}
523-
if rmatching.char_count == nchars
524-
&& !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count])
570+
} else if let Some(s) = item_camel.strip_suffix(&prev.name_camel)
571+
&& !self.is_allowed_prefix(s)
525572
{
526-
span_lint(
527-
cx,
528-
MODULE_NAME_REPETITIONS,
529-
ident.span,
530-
"item name ends with its containing module's name",
531-
);
573+
if !is_from_proc_macro(cx, item) {
574+
span_lint(
575+
cx,
576+
MODULE_NAME_REPETITIONS,
577+
ident.span,
578+
"item name ends with its containing module's name",
579+
);
580+
}
532581
}
533582
}
534583
}
535584

536-
if span_is_local(item.span) {
537-
match item.kind {
538-
ItemKind::Enum(_, def, _) => {
539-
self.check_variants(cx, item, &def);
540-
},
541-
ItemKind::Struct(_, VariantData::Struct { fields, .. }, _) => {
542-
self.check_fields(cx, item, fields);
543-
},
544-
_ => (),
545-
}
585+
if matches!(item.kind, ItemKind::Mod(..)) {
586+
self.modules.push(ModInfo {
587+
name: ident.name,
588+
name_camel: item_camel,
589+
is_public: cx.tcx.visibility(item.owner_id).is_public(),
590+
in_body_count: 0,
591+
});
546592
}
547-
self.modules.push((ident.name, item_camel, item.owner_id));
548593
}
549594
}

tests/ui/module_inception.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,13 @@ mod bar {
3838
mod bar {}
3939
}
4040

41+
mod with_inner_impl {
42+
struct S;
43+
impl S {
44+
fn f() {
45+
mod with_inner_impl {}
46+
}
47+
}
48+
}
49+
4150
fn main() {}

0 commit comments

Comments
 (0)