|
| 1 | +//! Lint for detecting modules that have the same name as their parent module. |
| 2 | +
|
| 3 | +use rustc_hir::{Body, Item, ItemKind}; |
| 4 | +use rustc_session::{declare_lint, impl_lint_pass}; |
| 5 | +use rustc_span::Symbol; |
| 6 | + |
| 7 | +use crate::{LateContext, LateLintPass, LintContext}; |
| 8 | + |
| 9 | +declare_lint! { |
| 10 | + /// The `module_inception` lint detects modules that have the same name as their parent module. |
| 11 | + /// |
| 12 | + /// ### Example |
| 13 | + /// |
| 14 | + /// ```rust,compile_fail |
| 15 | + /// mod foo { |
| 16 | + /// mod foo { |
| 17 | + /// pub fn bar() {} |
| 18 | + /// } |
| 19 | + /// } |
| 20 | + /// ``` |
| 21 | + /// |
| 22 | + /// {{produces}} |
| 23 | + /// |
| 24 | + /// ### Explanation |
| 25 | + /// |
| 26 | + /// A typical beginner mistake is to have `mod foo;` and again `mod foo { .. }` |
| 27 | + /// in `foo.rs`. The expectation is that items inside the inner `mod foo { .. }` |
| 28 | + /// are then available through `foo::x`, but they are only available through |
| 29 | + /// `foo::foo::x`. If this is done on purpose, it would be better to choose a |
| 30 | + /// more representative module name. |
| 31 | + pub MODULE_INCEPTION, |
| 32 | + Warn, |
| 33 | + "modules that have the same name as their parent module" |
| 34 | +} |
| 35 | + |
| 36 | +struct ModInfo { |
| 37 | + name: Symbol, |
| 38 | + /// How many bodies are between this module and the current lint pass position. |
| 39 | + /// |
| 40 | + /// Only the most recently seen module is updated when entering/exiting a body. |
| 41 | + in_body_count: u32, |
| 42 | +} |
| 43 | + |
| 44 | +pub(crate) struct ModuleInception { |
| 45 | + /// The module path the lint pass is in. |
| 46 | + modules: Vec<ModInfo>, |
| 47 | +} |
| 48 | + |
| 49 | +impl ModuleInception { |
| 50 | + pub(crate) fn new() -> Self { |
| 51 | + Self { modules: Vec::new() } |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +impl_lint_pass!(ModuleInception => [MODULE_INCEPTION]); |
| 56 | + |
| 57 | +impl<'tcx> LateLintPass<'tcx> for ModuleInception { |
| 58 | + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
| 59 | + if let ItemKind::Mod(ident, _) = item.kind { |
| 60 | + // Check if this module has the same name as its parent module |
| 61 | + if let [.., prev] = &*self.modules |
| 62 | + && prev.name == ident.name |
| 63 | + && prev.in_body_count == 0 |
| 64 | + && !item.span.from_expansion() |
| 65 | + { |
| 66 | + cx.span_lint(MODULE_INCEPTION, item.span, |lint| { |
| 67 | + lint.primary_message("module has the same name as its containing module"); |
| 68 | + }); |
| 69 | + } |
| 70 | + |
| 71 | + self.modules.push(ModInfo { name: ident.name, in_body_count: 0 }); |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + fn check_item_post(&mut self, _cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { |
| 76 | + if matches!(item.kind, ItemKind::Mod(..)) { |
| 77 | + self.modules.pop(); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + fn check_body(&mut self, _: &LateContext<'tcx>, _: &Body<'tcx>) { |
| 82 | + if let [.., last] = &mut *self.modules { |
| 83 | + last.in_body_count += 1; |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + fn check_body_post(&mut self, _: &LateContext<'tcx>, _: &Body<'tcx>) { |
| 88 | + if let [.., last] = &mut *self.modules { |
| 89 | + last.in_body_count -= 1; |
| 90 | + } |
| 91 | + } |
| 92 | +} |
0 commit comments