]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/module_style.rs
Auto merge of #8289 - jubnzv:unspecified-layout-union, r=camsteffen
[rust.git] / clippy_lints / src / module_style.rs
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     #[clippy::version = "1.57.0"]
36     pub MOD_MODULE_FILES,
37     restriction,
38     "checks that module layout is consistent"
39 }
40
41 declare_clippy_lint! {
42     /// ### What it does
43     /// Checks that module layout uses only mod.rs files.
44     ///
45     /// ### Why is this bad?
46     /// Having multiple module layout styles in a project can be confusing.
47     ///
48     /// ### Example
49     /// ```text
50     /// src/
51     ///   stuff/
52     ///     stuff_files.rs
53     ///   stuff.rs
54     ///   lib.rs
55     /// ```
56     /// Use instead:
57     /// ```text
58     /// src/
59     ///   stuff/
60     ///     stuff_files.rs
61     ///     mod.rs
62     ///   lib.rs
63     /// ```
64
65     #[clippy::version = "1.57.0"]
66     pub SELF_NAMED_MODULE_FILES,
67     restriction,
68     "checks that module layout is consistent"
69 }
70
71 pub struct ModStyle;
72
73 impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
74
75 impl EarlyLintPass for ModStyle {
76     fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
77         if cx.builder.lint_level(MOD_MODULE_FILES).0 == Level::Allow
78             && cx.builder.lint_level(SELF_NAMED_MODULE_FILES).0 == Level::Allow
79         {
80             return;
81         }
82
83         let files = cx.sess().source_map().files();
84
85         let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess().opts.working_dir {
86             p.to_string_lossy()
87         } else {
88             return;
89         };
90
91         // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
92         // `[path, to]` but not foo
93         let mut folder_segments = FxHashSet::default();
94         // `mod_folders` is all the unique folder names that contain a mod.rs file
95         let mut mod_folders = FxHashSet::default();
96         // `file_map` maps file names to the full path including the file name
97         // `{ foo => path/to/foo.rs, .. }
98         let mut file_map = FxHashMap::default();
99         for file in files.iter() {
100             match &file.name {
101                 FileName::Real(RealFileName::LocalPath(lp))
102                     if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) =>
103                 {
104                     let p = lp.to_string_lossy();
105                     let path = Path::new(p.trim_start_matches(trim_to_src.as_ref()));
106                     if let Some(stem) = path.file_stem() {
107                         file_map.insert(stem.to_os_string(), (file, path.to_owned()));
108                     }
109                     process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
110                     check_self_named_mod_exists(cx, path, file);
111                 },
112                 _ => {},
113             }
114         }
115
116         for folder in &folder_segments {
117             if !mod_folders.contains(folder) {
118                 if let Some((file, path)) = file_map.get(folder) {
119                     let mut correct = path.clone();
120                     correct.pop();
121                     correct.push(folder);
122                     correct.push("mod.rs");
123                     cx.struct_span_lint(
124                         SELF_NAMED_MODULE_FILES,
125                         Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
126                         |build| {
127                             let mut lint =
128                                 build.build(&format!("`mod.rs` files are required, found `{}`", path.display()));
129                             lint.help(&format!("move `{}` to `{}`", path.display(), correct.display(),));
130                             lint.emit();
131                         },
132                     );
133                 }
134             }
135         }
136     }
137 }
138
139 /// For each `path` we add each folder component to `folder_segments` and if the file name
140 /// is `mod.rs` we add it's parent folder to `mod_folders`.
141 fn process_paths_for_mod_files(
142     path: &Path,
143     folder_segments: &mut FxHashSet<OsString>,
144     mod_folders: &mut FxHashSet<OsString>,
145 ) {
146     let mut comp = path.components().rev().peekable();
147     let _ = comp.next();
148     if path.ends_with("mod.rs") {
149         mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default());
150     }
151     let folders = comp
152         .filter_map(|c| {
153             if let Component::Normal(s) = c {
154                 Some(s.to_os_string())
155             } else {
156                 None
157             }
158         })
159         .collect::<Vec<_>>();
160     folder_segments.extend(folders);
161 }
162
163 /// Checks every path for the presence of `mod.rs` files and emits the lint if found.
164 fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
165     if path.ends_with("mod.rs") {
166         let mut mod_file = path.to_path_buf();
167         mod_file.pop();
168         mod_file.set_extension("rs");
169
170         cx.struct_span_lint(
171             MOD_MODULE_FILES,
172             Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
173             |build| {
174                 let mut lint = build.build(&format!("`mod.rs` files are not allowed, found `{}`", path.display()));
175                 lint.help(&format!("move `{}` to `{}`", path.display(), mod_file.display(),));
176                 lint.emit();
177             },
178         );
179     }
180 }