Skip to content

Commit f9c9cfe

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 f9c9cfe

File tree

14 files changed

+114
-74
lines changed

14 files changed

+114
-74
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: 65 additions & 73 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
@@ -67,100 +66,93 @@ declare_clippy_lint! {
6766
"checks that module layout is consistent"
6867
}
6968

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

71+
#[derive(Default)]
72+
pub struct ModStyle {
73+
mod_folders: FxHashSet<PathBuf>,
74+
working_dir: Option<PathBuf>,
75+
}
76+
7477
impl EarlyLintPass for ModStyle {
7578
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
7679
if cx.builder.lint_level(MOD_MODULE_FILES).level == Level::Allow
7780
&& cx.builder.lint_level(SELF_NAMED_MODULE_FILES).level == Level::Allow
7881
{
7982
return;
8083
}
81-
82-
let files = cx.sess().source_map().files();
83-
84-
let Some(trim_to_src) = cx.sess().opts.working_dir.local_path() else {
84+
let Some(working_dir) = cx.sess().opts.working_dir.local_path() else {
8585
return;
8686
};
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
87+
self.mod_folders = cx
88+
.sess()
89+
.source_map()
90+
.files()
91+
.iter()
92+
.filter_map(|file| {
93+
let path = try_trim_path_prefix(file, working_dir)?;
94+
if path.extension()?.eq("rs") {
95+
check_mod_module(cx, path, file);
96+
let mut mod_folder = path.to_path_buf();
97+
mod_folder.pop();
98+
Some(mod_folder)
10999
} else {
110-
continue;
111-
};
112-
113-
if let Some(stem) = path.file_stem() {
114-
file_map.insert(stem, (file, path));
100+
None
115101
}
116-
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
117-
check_self_named_mod_exists(cx, path, file);
118-
}
102+
})
103+
.collect();
104+
self.working_dir = Some(working_dir.to_path_buf());
105+
}
106+
107+
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
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_self_named_module(cx, mod_path, &mod_file, &self.mod_folders);
119116
}
117+
}
118+
}
120119

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-
}
120+
fn try_trim_path_prefix<'a>(file: &'a SourceFile, prefix: &'a Path) -> Option<&'a Path> {
121+
if let FileName::Real(name) = &file.name
122+
&& let Some(mut path) = name.local_path()
123+
&& file.cnum == LOCAL_CRATE
124+
{
125+
if !path.is_relative() {
126+
path = path.strip_prefix(prefix).ok()?;
139127
}
128+
Some(path)
129+
} else {
130+
None
140131
}
141132
}
142133

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());
134+
fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile, mod_folders: &FxHashSet<PathBuf>) {
135+
if !path.ends_with("mod.rs")
136+
&& let mut mod_folder = path.with_extension("")
137+
&& mod_folders.contains(&mod_folder)
138+
{
139+
span_lint_and_then(
140+
cx,
141+
SELF_NAMED_MODULE_FILES,
142+
Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
143+
format!("`mod.rs` files are required, found `{}`", path.display()),
144+
|diag| {
145+
mod_folder.push("mod.rs");
146+
diag.help(format!("move `{}` to `{}`", path.display(), mod_folder.display()));
147+
},
148+
);
154149
}
155-
let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None });
156-
folder_segments.extend(folders);
157150
}
158151

159-
/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
160152
/// We should not emit a lint for test modules in the presence of `mod.rs`.
161153
/// 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)
162154
/// for code-sharing between tests.
163-
fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
155+
fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
164156
if path.ends_with("mod.rs") && !path.starts_with("tests") {
165157
span_lint_and_then(
166158
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)