|
1 | 1 | use clippy_config::Conf;
|
2 | 2 | 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; |
6 | 3 | 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}; |
7 | 5 | 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}; |
9 | 7 | use rustc_lint::{LateContext, LateLintPass};
|
10 | 8 | use rustc_session::impl_lint_pass;
|
11 | 9 | use rustc_span::symbol::Symbol;
|
@@ -157,8 +155,15 @@ declare_clippy_lint! {
|
157 | 155 | "structs where all fields share a prefix/postfix or contain the name of the struct"
|
158 | 156 | }
|
159 | 157 |
|
| 158 | +struct ModInfo { |
| 159 | + name: Symbol, |
| 160 | + name_camel: String, |
| 161 | + is_public: bool, |
| 162 | + in_body_count: u32, |
| 163 | +} |
| 164 | + |
160 | 165 | pub struct ItemNameRepetitions {
|
161 |
| - modules: Vec<(Symbol, String, OwnerId)>, |
| 166 | + modules: Vec<ModInfo>, |
162 | 167 | enum_threshold: u64,
|
163 | 168 | struct_threshold: u64,
|
164 | 169 | avoid_breaking_exported_api: bool,
|
@@ -456,82 +461,118 @@ fn check_enum_tuple_path_match(variant_name: &str, variant_data: VariantData<'_>
|
456 | 461 | }
|
457 | 462 |
|
458 | 463 | impl LateLintPass<'_> for ItemNameRepetitions {
|
459 |
| - fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { |
460 |
| - let Some(_ident) = item.kind.ident() else { return }; |
| 464 | + fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) { |
| 465 | + if matches!(item.kind, ItemKind::Mod(..)) { |
| 466 | + let prev = self.modules.pop(); |
| 467 | + debug_assert!(prev.is_some()); |
| 468 | + } |
| 469 | + } |
| 470 | + |
| 471 | + fn check_body(&mut self, _: &LateContext<'_>, _: &Body<'_>) { |
| 472 | + if let [.., last] = &mut *self.modules { |
| 473 | + last.in_body_count += 1; |
| 474 | + } |
| 475 | + } |
461 | 476 |
|
462 |
| - let last = self.modules.pop(); |
463 |
| - assert!(last.is_some()); |
| 477 | + fn check_body_post(&mut self, _: &LateContext<'_>, _: &Body<'_>) { |
| 478 | + if let [.., last] = &mut *self.modules { |
| 479 | + last.in_body_count -= 1; |
| 480 | + } |
464 | 481 | }
|
465 | 482 |
|
466 | 483 | fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
467 |
| - let Some(ident) = item.kind.ident() else { return }; |
| 484 | + let ident = match item.kind { |
| 485 | + ItemKind::Mod(ident, _) => { |
| 486 | + if let [.., prev] = &*self.modules |
| 487 | + && prev.name == ident.name |
| 488 | + && prev.in_body_count == 0 |
| 489 | + && (!self.allow_private_module_inception || prev.is_public) |
| 490 | + && !item.span.from_expansion() |
| 491 | + && !is_from_proc_macro(cx, item) |
| 492 | + { |
| 493 | + span_lint( |
| 494 | + cx, |
| 495 | + MODULE_INCEPTION, |
| 496 | + item.span, |
| 497 | + "module has the same name as its containing module", |
| 498 | + ); |
| 499 | + } |
| 500 | + ident |
| 501 | + }, |
| 502 | + |
| 503 | + ItemKind::Enum(ident, def, _) => { |
| 504 | + if !ident.span.in_external_macro(cx.tcx.sess.source_map()) { |
| 505 | + self.check_variants(cx, item, &def); |
| 506 | + } |
| 507 | + ident |
| 508 | + }, |
| 509 | + ItemKind::Struct(ident, data, _) => { |
| 510 | + if let VariantData::Struct { fields, .. } = data |
| 511 | + && !ident.span.in_external_macro(cx.tcx.sess.source_map()) |
| 512 | + { |
| 513 | + self.check_fields(cx, item, fields); |
| 514 | + } |
| 515 | + ident |
| 516 | + }, |
| 517 | + |
| 518 | + ItemKind::Const(ident, ..) |
| 519 | + | ItemKind::ExternCrate(_, ident) |
| 520 | + | ItemKind::Fn { ident, .. } |
| 521 | + | ItemKind::Macro(ident, ..) |
| 522 | + | ItemKind::Static(ident, ..) |
| 523 | + | ItemKind::Trait(_, _, ident, ..) |
| 524 | + | ItemKind::TraitAlias(ident, ..) |
| 525 | + | ItemKind::TyAlias(ident, ..) |
| 526 | + | ItemKind::Union(ident, ..) |
| 527 | + | ItemKind::Use(_, UseKind::Single(ident))=> ident, |
| 528 | + |
| 529 | + ItemKind::ForeignMod { .. } |
| 530 | + | ItemKind::GlobalAsm { .. } |
| 531 | + | ItemKind::Impl(_) |
| 532 | + | ItemKind::Use(..) => return, |
| 533 | + }; |
468 | 534 |
|
469 | 535 | let item_name = ident.name.as_str();
|
470 | 536 | let item_camel = to_camel_case(item_name);
|
471 |
| - if !item.span.from_expansion() && is_present_in_source(cx, item.span) |
472 |
| - && let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules |
473 |
| - // constants don't have surrounding modules |
474 |
| - && !mod_camel.is_empty() |
| 537 | + |
| 538 | + if let [.., prev] = &*self.modules |
| 539 | + && prev.is_public |
| 540 | + && prev.in_body_count == 0 |
| 541 | + && item_camel.len() > prev.name_camel.len() |
| 542 | + && !item.span.from_expansion() |
| 543 | + && cx.tcx.visibility(item.owner_id).is_public() |
475 | 544 | {
|
476 |
| - if mod_name == &ident.name |
477 |
| - && let ItemKind::Mod(..) = item.kind |
478 |
| - && (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public()) |
| 545 | + if let Some(s) = item_camel.strip_prefix(&prev.name_camel) |
| 546 | + && let Some(c) = s.chars().next() |
| 547 | + && (c == '_' || c.is_uppercase() || c.is_numeric()) |
| 548 | + && !is_from_proc_macro(cx, item) |
479 | 549 | {
|
480 | 550 | span_lint(
|
481 | 551 | cx,
|
482 |
| - MODULE_INCEPTION, |
483 |
| - item.span, |
484 |
| - "module has the same name as its containing module", |
| 552 | + MODULE_NAME_REPETITIONS, |
| 553 | + ident.span, |
| 554 | + "item name starts with its containing module's name", |
485 | 555 | );
|
486 |
| - } |
487 |
| - |
488 |
| - // The `module_name_repetitions` lint should only trigger if the item has the module in its |
489 |
| - // name. Having the same name is accepted. |
490 |
| - if cx.tcx.visibility(item.owner_id).is_public() |
491 |
| - && cx.tcx.visibility(mod_owner_id.def_id).is_public() |
492 |
| - && item_camel.len() > mod_camel.len() |
| 556 | + } else if let Some(s) = item_camel.strip_suffix(&prev.name_camel) |
| 557 | + && !self.is_allowed_prefix(s) |
| 558 | + && !is_from_proc_macro(cx, item) |
493 | 559 | {
|
494 |
| - let matching = count_match_start(mod_camel, &item_camel); |
495 |
| - let rmatching = count_match_end(mod_camel, &item_camel); |
496 |
| - let nchars = mod_camel.chars().count(); |
497 |
| - |
498 |
| - let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric(); |
499 |
| - |
500 |
| - if matching.char_count == nchars { |
501 |
| - match item_camel.chars().nth(nchars) { |
502 |
| - Some(c) if is_word_beginning(c) => span_lint( |
503 |
| - cx, |
504 |
| - MODULE_NAME_REPETITIONS, |
505 |
| - ident.span, |
506 |
| - "item name starts with its containing module's name", |
507 |
| - ), |
508 |
| - _ => (), |
509 |
| - } |
510 |
| - } |
511 |
| - if rmatching.char_count == nchars |
512 |
| - && !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count]) |
513 |
| - { |
514 |
| - span_lint( |
515 |
| - cx, |
516 |
| - MODULE_NAME_REPETITIONS, |
517 |
| - ident.span, |
518 |
| - "item name ends with its containing module's name", |
519 |
| - ); |
520 |
| - } |
| 560 | + span_lint( |
| 561 | + cx, |
| 562 | + MODULE_NAME_REPETITIONS, |
| 563 | + ident.span, |
| 564 | + "item name ends with its containing module's name", |
| 565 | + ); |
521 | 566 | }
|
522 | 567 | }
|
523 | 568 |
|
524 |
| - if span_is_local(item.span) { |
525 |
| - match item.kind { |
526 |
| - ItemKind::Enum(_, def, _) => { |
527 |
| - self.check_variants(cx, item, &def); |
528 |
| - }, |
529 |
| - ItemKind::Struct(_, VariantData::Struct { fields, .. }, _) => { |
530 |
| - self.check_fields(cx, item, fields); |
531 |
| - }, |
532 |
| - _ => (), |
533 |
| - } |
| 569 | + if matches!(item.kind, ItemKind::Mod(..)) { |
| 570 | + self.modules.push(ModInfo { |
| 571 | + name: ident.name, |
| 572 | + name_camel: item_camel, |
| 573 | + is_public: cx.tcx.visibility(item.owner_id).is_public(), |
| 574 | + in_body_count: 0, |
| 575 | + }); |
534 | 576 | }
|
535 |
| - self.modules.push((ident.name, item_camel, item.owner_id)); |
536 | 577 | }
|
537 | 578 | }
|
0 commit comments