Skip to content

Commit 31738b1

Browse files
committed
Add module_style lint to restriction
Add tests for disallowed_mod in ui-cargo test section Use correct algorithm to determine if mod.rs is missing Move to two lints and remove config option Switch lint names so they read "warn on ..." Emit the same help info for self_named_mod_file warnings Bail when both lints are Allow Reword help message for both module_style lints
1 parent 1fc1aee commit 31738b1

File tree

23 files changed

+296
-0
lines changed

23 files changed

+296
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,6 +2796,7 @@ Released 2018-09-13
27962796
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
27972797
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
27982798
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
2799+
[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
27992800
[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
28002801
[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
28012802
[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
@@ -2904,6 +2905,7 @@ Released 2018-09-13
29042905
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
29052906
[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
29062907
[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
2908+
[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files
29072909
[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
29082910
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
29092911
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse

clippy_lints/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ mod missing_const_for_fn;
274274
mod missing_doc;
275275
mod missing_enforced_import_rename;
276276
mod missing_inline;
277+
mod module_style;
277278
mod modulo_arithmetic;
278279
mod multiple_crate_versions;
279280
mod mut_key;
@@ -826,6 +827,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
826827
missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
827828
missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
828829
missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
830+
module_style::MOD_MODULE_FILES,
831+
module_style::SELF_NAMED_MODULE_FILES,
829832
modulo_arithmetic::MODULO_ARITHMETIC,
830833
multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
831834
mut_key::MUTABLE_KEY_TYPE,
@@ -1035,6 +1038,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10351038
LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS),
10361039
LintId::of(missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES),
10371040
LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS),
1041+
LintId::of(module_style::MOD_MODULE_FILES),
1042+
LintId::of(module_style::SELF_NAMED_MODULE_FILES),
10381043
LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
10391044
LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
10401045
LintId::of(panic_unimplemented::PANIC),
@@ -2107,6 +2112,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
21072112
store.register_late_pass(|| box manual_map::ManualMap);
21082113
store.register_late_pass(move || box if_then_some_else_none::IfThenSomeElseNone::new(msrv));
21092114
store.register_early_pass(|| box bool_assert_comparison::BoolAssertComparison);
2115+
store.register_early_pass(move || box module_style::ModStyle);
21102116
store.register_late_pass(|| box unused_async::UnusedAsync);
21112117
let disallowed_types = conf.disallowed_types.iter().cloned().collect::<FxHashSet<_>>();
21122118
store.register_late_pass(move || box disallowed_type::DisallowedType::new(&disallowed_types));

clippy_lints/src/module_style.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use std::{
2+
ffi::OsString,
3+
path::{Component, Path},
4+
};
5+
6+
use rustc_ast::ast;
7+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8+
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
9+
use rustc_session::{declare_tool_lint, impl_lint_pass};
10+
use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext};
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks that module layout uses only self named module files, bans mod.rs files.
15+
///
16+
/// ### Why is this bad?
17+
/// Having multiple module layout styles in a project can be confusing.
18+
///
19+
/// ### Example
20+
/// ```text
21+
/// src/
22+
/// stuff/
23+
/// stuff_files.rs
24+
/// mod.rs
25+
/// lib.rs
26+
/// ```
27+
/// Use instead:
28+
/// ```text
29+
/// src/
30+
/// stuff/
31+
/// stuff_files.rs
32+
/// stuff.rs
33+
/// lib.rs
34+
/// ```
35+
pub MOD_MODULE_FILES,
36+
restriction,
37+
"checks that module layout is consistent"
38+
}
39+
40+
declare_clippy_lint! {
41+
/// ### What it does
42+
/// Checks that module layout uses only mod.rs files.
43+
///
44+
/// ### Why is this bad?
45+
/// Having multiple module layout styles in a project can be confusing.
46+
///
47+
/// ### Example
48+
/// ```text
49+
/// src/
50+
/// stuff/
51+
/// stuff_files.rs
52+
/// stuff.rs
53+
/// lib.rs
54+
/// ```
55+
/// Use instead:
56+
/// ```text
57+
/// src/
58+
/// stuff/
59+
/// stuff_files.rs
60+
/// mod.rs
61+
/// lib.rs
62+
/// ```
63+
64+
pub SELF_NAMED_MODULE_FILES,
65+
restriction,
66+
"checks that module layout is consistent"
67+
}
68+
69+
pub struct ModStyle;
70+
71+
impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
72+
73+
impl EarlyLintPass for ModStyle {
74+
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
75+
if cx.builder.lint_level(MOD_MODULE_FILES).0 == Level::Allow
76+
&& cx.builder.lint_level(SELF_NAMED_MODULE_FILES).0 == Level::Allow
77+
{
78+
return;
79+
}
80+
81+
let files = cx.sess.source_map().files();
82+
83+
let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess.working_dir {
84+
p.to_string_lossy()
85+
} else {
86+
return;
87+
};
88+
89+
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
90+
// `[path, to]` but not foo
91+
let mut folder_segments = FxHashSet::default();
92+
// `mod_folders` is all the unique folder names that contain a mod.rs file
93+
let mut mod_folders = FxHashSet::default();
94+
// `file_map` maps file names to the full path including the file name
95+
// `{ foo => path/to/foo.rs, .. }
96+
let mut file_map = FxHashMap::default();
97+
for file in files.iter() {
98+
match &file.name {
99+
FileName::Real(RealFileName::LocalPath(lp))
100+
if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) =>
101+
{
102+
let p = lp.to_string_lossy();
103+
let path = Path::new(p.trim_start_matches(trim_to_src.as_ref()));
104+
if let Some(stem) = path.file_stem() {
105+
file_map.insert(stem.to_os_string(), (file, path.to_owned()));
106+
}
107+
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
108+
check_self_named_mod_exists(cx, path, file);
109+
}
110+
_ => {},
111+
}
112+
}
113+
114+
for folder in &folder_segments {
115+
if !mod_folders.contains(folder) {
116+
if let Some((file, path)) = file_map.get(folder) {
117+
let mut correct = path.clone();
118+
correct.pop();
119+
correct.push(folder);
120+
correct.push("mod.rs");
121+
cx.struct_span_lint(
122+
SELF_NAMED_MODULE_FILES,
123+
Span::new(file.start_pos, file.start_pos, SyntaxContext::root()),
124+
|build| {
125+
let mut lint =
126+
build.build(&format!("`mod.rs` files are required, found `{}`", path.display()));
127+
lint.help(&format!("move `{}` to `{}`", path.display(), correct.display(),));
128+
lint.emit();
129+
},
130+
);
131+
}
132+
}
133+
}
134+
}
135+
}
136+
137+
/// For each `path` we add each folder component to `folder_segments` and if the file name
138+
/// is `mod.rs` we add it's parent folder to `mod_folders`.
139+
fn process_paths_for_mod_files(
140+
path: &Path,
141+
folder_segments: &mut FxHashSet<OsString>,
142+
mod_folders: &mut FxHashSet<OsString>,
143+
) {
144+
let mut comp = path.components().rev().peekable();
145+
let _ = comp.next();
146+
if path.ends_with("mod.rs") {
147+
mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default());
148+
}
149+
let folders = comp
150+
.filter_map(|c| {
151+
if let Component::Normal(s) = c {
152+
Some(s.to_os_string())
153+
} else {
154+
None
155+
}
156+
})
157+
.collect::<Vec<_>>();
158+
folder_segments.extend(folders);
159+
}
160+
161+
/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
162+
fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
163+
if path.ends_with("mod.rs") {
164+
let mut mod_file = path.to_path_buf();
165+
mod_file.pop();
166+
mod_file.set_extension("rs");
167+
168+
cx.struct_span_lint(
169+
MOD_MODULE_FILES,
170+
Span::new(file.start_pos, file.start_pos, SyntaxContext::root()),
171+
|build| {
172+
let mut lint = build.build(&format!("`mod.rs` files are not allowed, found `{}`", path.display()));
173+
lint.help(&format!("move `{}` to `{}`", path.display(), mod_file.display(),));
174+
lint.emit();
175+
},
176+
);
177+
}
178+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "fail"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod stuff;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod most;
2+
3+
pub struct Inner;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub struct Snarks;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod inner;
2+
3+
pub struct Thing;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![warn(clippy::self_named_module_files)]
2+
3+
mod bad;
4+
5+
fn main() {
6+
let _ = bad::Thing;
7+
let _ = bad::inner::stuff::Inner;
8+
let _ = bad::inner::stuff::most::Snarks;
9+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: `mod.rs` files are required, found `/bad/inner.rs`
2+
--> $DIR/bad/inner.rs:1:1
3+
|
4+
LL | pub mod stuff;
5+
| ^
6+
|
7+
= note: `-D clippy::self-named-module-files` implied by `-D warnings`
8+
= help: move `/bad/inner.rs` to `/bad/inner/mod.rs`
9+
10+
error: `mod.rs` files are required, found `/bad/inner/stuff.rs`
11+
--> $DIR/bad/inner/stuff.rs:1:1
12+
|
13+
LL | pub mod most;
14+
| ^
15+
|
16+
= help: move `/bad/inner/stuff.rs` to `/bad/inner/stuff/mod.rs`
17+
18+
error: aborting due to 2 previous errors
19+

0 commit comments

Comments
 (0)