]>
Commit | Line | Data |
---|---|---|
c295e0f8 XL |
1 | use rustc_ast::ast; |
2 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | |
3 | use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; | |
4 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
2b03887a | 5 | use rustc_span::{FileName, SourceFile, Span, SyntaxContext}; |
04454e1e FG |
6 | use std::ffi::OsStr; |
7 | use std::path::{Component, Path}; | |
c295e0f8 XL |
8 | |
9 | declare_clippy_lint! { | |
10 | /// ### What it does | |
04454e1e | 11 | /// Checks that module layout uses only self named module files, bans `mod.rs` files. |
c295e0f8 XL |
12 | /// |
13 | /// ### Why is this bad? | |
14 | /// Having multiple module layout styles in a project can be confusing. | |
15 | /// | |
16 | /// ### Example | |
17 | /// ```text | |
18 | /// src/ | |
19 | /// stuff/ | |
20 | /// stuff_files.rs | |
21 | /// mod.rs | |
22 | /// lib.rs | |
23 | /// ``` | |
24 | /// Use instead: | |
25 | /// ```text | |
26 | /// src/ | |
27 | /// stuff/ | |
28 | /// stuff_files.rs | |
29 | /// stuff.rs | |
30 | /// lib.rs | |
31 | /// ``` | |
a2a8927a | 32 | #[clippy::version = "1.57.0"] |
c295e0f8 XL |
33 | pub MOD_MODULE_FILES, |
34 | restriction, | |
35 | "checks that module layout is consistent" | |
36 | } | |
37 | ||
38 | declare_clippy_lint! { | |
39 | /// ### What it does | |
04454e1e | 40 | /// Checks that module layout uses only `mod.rs` files. |
c295e0f8 XL |
41 | /// |
42 | /// ### Why is this bad? | |
43 | /// Having multiple module layout styles in a project can be confusing. | |
44 | /// | |
45 | /// ### Example | |
46 | /// ```text | |
47 | /// src/ | |
48 | /// stuff/ | |
49 | /// stuff_files.rs | |
50 | /// stuff.rs | |
51 | /// lib.rs | |
52 | /// ``` | |
53 | /// Use instead: | |
54 | /// ```text | |
55 | /// src/ | |
56 | /// stuff/ | |
57 | /// stuff_files.rs | |
58 | /// mod.rs | |
59 | /// lib.rs | |
60 | /// ``` | |
61 | ||
a2a8927a | 62 | #[clippy::version = "1.57.0"] |
c295e0f8 XL |
63 | pub SELF_NAMED_MODULE_FILES, |
64 | restriction, | |
65 | "checks that module layout is consistent" | |
66 | } | |
67 | ||
68 | pub struct ModStyle; | |
69 | ||
70 | impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]); | |
71 | ||
72 | impl EarlyLintPass for ModStyle { | |
73 | fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { | |
74 | if cx.builder.lint_level(MOD_MODULE_FILES).0 == Level::Allow | |
75 | && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).0 == Level::Allow | |
76 | { | |
77 | return; | |
78 | } | |
79 | ||
5099ac24 | 80 | let files = cx.sess().source_map().files(); |
c295e0f8 | 81 | |
2b03887a | 82 | let Some(trim_to_src) = cx.sess().opts.working_dir.local_path() else { return }; |
c295e0f8 XL |
83 | |
84 | // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives | |
85 | // `[path, to]` but not foo | |
86 | let mut folder_segments = FxHashSet::default(); | |
87 | // `mod_folders` is all the unique folder names that contain a mod.rs file | |
88 | let mut mod_folders = FxHashSet::default(); | |
89 | // `file_map` maps file names to the full path including the file name | |
90 | // `{ foo => path/to/foo.rs, .. } | |
91 | let mut file_map = FxHashMap::default(); | |
92 | for file in files.iter() { | |
2b03887a | 93 | if let FileName::Real(name) = &file.name && let Some(lp) = name.local_path() { |
04454e1e FG |
94 | let path = if lp.is_relative() { |
95 | lp | |
96 | } else if let Ok(relative) = lp.strip_prefix(trim_to_src) { | |
97 | relative | |
98 | } else { | |
99 | continue; | |
100 | }; | |
101 | ||
102 | if let Some(stem) = path.file_stem() { | |
103 | file_map.insert(stem, (file, path)); | |
104 | } | |
105 | process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders); | |
106 | check_self_named_mod_exists(cx, path, file); | |
c295e0f8 XL |
107 | } |
108 | } | |
109 | ||
110 | for folder in &folder_segments { | |
111 | if !mod_folders.contains(folder) { | |
112 | if let Some((file, path)) = file_map.get(folder) { | |
04454e1e | 113 | let mut correct = path.to_path_buf(); |
c295e0f8 XL |
114 | correct.pop(); |
115 | correct.push(folder); | |
116 | correct.push("mod.rs"); | |
117 | cx.struct_span_lint( | |
118 | SELF_NAMED_MODULE_FILES, | |
119 | Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), | |
2b03887a FG |
120 | format!("`mod.rs` files are required, found `{}`", path.display()), |
121 | |lint| lint.help(format!("move `{}` to `{}`", path.display(), correct.display(),)), | |
c295e0f8 XL |
122 | ); |
123 | } | |
124 | } | |
125 | } | |
126 | } | |
127 | } | |
128 | ||
129 | /// For each `path` we add each folder component to `folder_segments` and if the file name | |
130 | /// is `mod.rs` we add it's parent folder to `mod_folders`. | |
04454e1e FG |
131 | fn process_paths_for_mod_files<'a>( |
132 | path: &'a Path, | |
133 | folder_segments: &mut FxHashSet<&'a OsStr>, | |
134 | mod_folders: &mut FxHashSet<&'a OsStr>, | |
c295e0f8 XL |
135 | ) { |
136 | let mut comp = path.components().rev().peekable(); | |
9ffffee4 | 137 | let _: Option<_> = comp.next(); |
c295e0f8 | 138 | if path.ends_with("mod.rs") { |
04454e1e | 139 | mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default()); |
c295e0f8 | 140 | } |
04454e1e | 141 | let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None }); |
c295e0f8 XL |
142 | folder_segments.extend(folders); |
143 | } | |
144 | ||
145 | /// Checks every path for the presence of `mod.rs` files and emits the lint if found. | |
146 | fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { | |
147 | if path.ends_with("mod.rs") { | |
148 | let mut mod_file = path.to_path_buf(); | |
149 | mod_file.pop(); | |
150 | mod_file.set_extension("rs"); | |
151 | ||
152 | cx.struct_span_lint( | |
153 | MOD_MODULE_FILES, | |
154 | Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), | |
2b03887a FG |
155 | format!("`mod.rs` files are not allowed, found `{}`", path.display()), |
156 | |lint| lint.help(format!("move `{}` to `{}`", path.display(), mod_file.display())), | |
c295e0f8 XL |
157 | ); |
158 | } | |
159 | } |