]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/module_style.rs
Rollup merge of #88651 - AGSaidi:monotonize-inner-64b-aarch64, r=dtolnay
[rust.git] / src / tools / clippy / 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     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.opts.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(), None),
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(), None),
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 }