Skip to content

Commit db5887b

Browse files
committed
fix self_named_module_files: track module with relative path
tracks each sub folder by its relative path from crate's working directory instead of individual path segments changelog: none Signed-off-by: Zihan <[email protected]>
1 parent e6b63d1 commit db5887b

File tree

14 files changed

+120
-79
lines changed

14 files changed

+120
-79
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
670670
store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10));
671671
store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf)));
672672
store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison));
673-
store.register_early_pass(move || Box::new(module_style::ModStyle));
673+
store.register_early_pass(move || Box::new(module_style::ModStyle::default()));
674674
store.register_late_pass(|_| Box::<unused_async::UnusedAsync>::default());
675675
store.register_late_pass(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf)));
676676
store.register_late_pass(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf)));

clippy_lints/src/module_style.rs

Lines changed: 71 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use rustc_ast::ast;
3-
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2+
use rustc_ast::ast::{self, Inline, ItemKind, ModKind};
3+
use rustc_data_structures::fx::FxHashSet;
44
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
55
use rustc_session::impl_lint_pass;
66
use rustc_span::def_id::LOCAL_CRATE;
77
use rustc_span::{FileName, SourceFile, Span, SyntaxContext};
8-
use std::ffi::OsStr;
9-
use std::path::{Component, Path};
8+
use std::path::{Path, PathBuf};
109

1110
declare_clippy_lint! {
1211
/// ### What it does
@@ -60,107 +59,101 @@ declare_clippy_lint! {
6059
/// mod.rs
6160
/// lib.rs
6261
/// ```
63-
6462
#[clippy::version = "1.57.0"]
6563
pub SELF_NAMED_MODULE_FILES,
6664
restriction,
6765
"checks that module layout is consistent"
6866
}
6967

70-
pub struct ModStyle;
71-
7268
impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
7369

70+
#[derive(Default)]
71+
pub struct ModStyle {
72+
mod_folders: FxHashSet<PathBuf>,
73+
working_dir: Option<PathBuf>,
74+
}
75+
7476
impl EarlyLintPass for ModStyle {
7577
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
78+
let Some(working_dir) = cx.sess().opts.working_dir.local_path() else {
79+
return;
80+
};
81+
if cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level != Level::Allow {
82+
self.mod_folders = cx
83+
.sess()
84+
.source_map()
85+
.files()
86+
.iter()
87+
.filter_map(|file| {
88+
let path = try_trim_path_prefix(file, working_dir)?;
89+
if path.extension()?.eq("rs") {
90+
let mut mod_folder = path.to_path_buf();
91+
mod_folder.pop();
92+
Some(mod_folder)
93+
} else {
94+
None
95+
}
96+
})
97+
.collect();
98+
}
99+
self.working_dir = Some(working_dir.to_path_buf());
100+
}
101+
102+
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
76103
if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow
77104
&& cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow
78105
{
79106
return;
80107
}
81-
82-
let files = cx.sess().source_map().files();
83-
84-
let Some(trim_to_src) = cx.sess().opts.working_dir.local_path() else {
85-
return;
86-
};
87-
88-
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
89-
// `[path, to]` but not foo
90-
let mut folder_segments = FxIndexSet::default();
91-
// `mod_folders` is all the unique folder names that contain a mod.rs file
92-
let mut mod_folders = FxHashSet::default();
93-
// `file_map` maps file names to the full path including the file name
94-
// `{ foo => path/to/foo.rs, .. }
95-
let mut file_map = FxHashMap::default();
96-
for file in files.iter() {
97-
if let FileName::Real(name) = &file.name
98-
&& let Some(lp) = name.local_path()
99-
&& file.cnum == LOCAL_CRATE
100-
{
101-
// [#8887](https://github.com/rust-lang/rust-clippy/issues/8887)
102-
// Only check files in the current crate.
103-
// Fix false positive that crate dependency in workspace sub directory
104-
// is checked unintentionally.
105-
let path = if lp.is_relative() {
106-
lp
107-
} else if let Ok(relative) = lp.strip_prefix(trim_to_src) {
108-
relative
109-
} else {
110-
continue;
111-
};
112-
113-
if let Some(stem) = path.file_stem() {
114-
file_map.insert(stem, (file, path));
115-
}
116-
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
117-
check_self_named_mod_exists(cx, path, file);
118-
}
108+
if let ItemKind::Mod(.., mod_kind) = &item.kind
109+
&& let ModKind::Loaded(_, inline, mod_spans, _) = mod_kind
110+
&& matches!(inline, Inline::No)
111+
&& let mod_file = cx.sess().source_map().lookup_source_file(mod_spans.inner_span.lo())
112+
&& let Some(working_dir) = self.working_dir.as_ref()
113+
&& let Some(mod_path) = try_trim_path_prefix(&mod_file, working_dir.as_path())
114+
{
115+
check_mod_module(cx, mod_path, &mod_file);
116+
check_self_named_module(cx, mod_path, &mod_file, &self.mod_folders);
119117
}
118+
}
119+
}
120120

121-
for folder in &folder_segments {
122-
if !mod_folders.contains(folder)
123-
&& let Some((file, path)) = file_map.get(folder)
124-
{
125-
span_lint_and_then(
126-
cx,
127-
SELF_NAMED_MODULE_FILES,
128-
Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
129-
format!("`mod.rs` files are required, found `{}`", path.display()),
130-
|diag| {
131-
let mut correct = path.to_path_buf();
132-
correct.pop();
133-
correct.push(folder);
134-
correct.push("mod.rs");
135-
diag.help(format!("move `{}` to `{}`", path.display(), correct.display(),));
136-
},
137-
);
138-
}
121+
fn try_trim_path_prefix<'a>(file: &'a SourceFile, prefix: &'a Path) -> Option<&'a Path> {
122+
if let FileName::Real(name) = &file.name
123+
&& let Some(mut path) = name.local_path()
124+
&& file.cnum == LOCAL_CRATE
125+
{
126+
if !path.is_relative() {
127+
path = path.strip_prefix(prefix).ok()?;
139128
}
129+
Some(path)
130+
} else {
131+
None
140132
}
141133
}
142134

143-
/// For each `path` we add each folder component to `folder_segments` and if the file name
144-
/// is `mod.rs` we add it's parent folder to `mod_folders`.
145-
fn process_paths_for_mod_files<'a>(
146-
path: &'a Path,
147-
folder_segments: &mut FxIndexSet<&'a OsStr>,
148-
mod_folders: &mut FxHashSet<&'a OsStr>,
149-
) {
150-
let mut comp = path.components().rev().peekable();
151-
let _: Option<_> = comp.next();
152-
if path.ends_with("mod.rs") {
153-
mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default());
135+
fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile, mod_folders: &FxHashSet<PathBuf>) {
136+
if !path.ends_with("mod.rs")
137+
&& let mut mod_folder = path.with_extension("")
138+
&& mod_folders.contains(&mod_folder)
139+
{
140+
span_lint_and_then(
141+
cx,
142+
SELF_NAMED_MODULE_FILES,
143+
Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
144+
format!("`mod.rs` files are required, found `{}`", path.display()),
145+
|diag| {
146+
mod_folder.push("mod.rs");
147+
diag.help(format!("move `{}` to `{}`", path.display(), mod_folder.display()));
148+
},
149+
);
154150
}
155-
let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None });
156-
folder_segments.extend(folders);
157151
}
158152

159-
/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
160153
/// We should not emit a lint for test modules in the presence of `mod.rs`.
161154
/// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test)
162155
/// for code-sharing between tests.
163-
fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
156+
fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
164157
if path.ends_with("mod.rs") && !path.starts_with("tests") {
165158
span_lint_and_then(
166159
cx,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: `mod.rs` files are required, found `src/foo.rs`
2+
--> src/foo.rs:1:1
3+
|
4+
1 | pub mod bar;
5+
| ^
6+
|
7+
= help: move `src/foo.rs` to `src/foo/mod.rs`
8+
= note: `-D clippy::self-named-module-files` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::self_named_module_files)]`
10+
11+
error: could not compile `duplicated-mod-names-14697` (lib) due to 1 previous error
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Should trigger when multiple mods with the same name exist and not all of them follow self-named convention.
2+
# See issue #14697.
3+
[package]
4+
name = "duplicated-mod-names-14697"
5+
version = "0.1.0"
6+
edition = "2024"
7+
publish = false
8+
9+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10+
11+
[dependencies]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod bar;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#![warn(clippy::self_named_module_files)]
2+
3+
pub mod foo;
4+
pub mod other;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod foo;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Should not produce FP when irrelavant path segment shares the same name with module.
2+
# See issue #10271 and #11916.
3+
[package]
4+
name = "segment-with-mod-name-10271-11916"
5+
version = "0.1.0"
6+
edition = "2024"
7+
publish = false
8+
9+
[workspace]
10+
members = ["foo/bar"]

0 commit comments

Comments
 (0)