]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/module_style.rs
Merge commit '984330a6ee3c4d15626685d6dc8b7b759ff630bd' into clippyup
[rust.git] / clippy_lints / src / module_style.rs
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};
5 use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext};
6 use std::ffi::OsStr;
7 use std::path::{Component, Path};
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Checks that module layout uses only self named module files, bans `mod.rs` files.
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     /// ```
32     #[clippy::version = "1.57.0"]
33     pub MOD_MODULE_FILES,
34     restriction,
35     "checks that module layout is consistent"
36 }
37
38 declare_clippy_lint! {
39     /// ### What it does
40     /// Checks that module layout uses only `mod.rs` files.
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
62     #[clippy::version = "1.57.0"]
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
80         let files = cx.sess().source_map().files();
81
82         let RealFileName::LocalPath(trim_to_src) = &cx.sess().opts.working_dir else { return };
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() {
93             if let FileName::Real(RealFileName::LocalPath(lp)) = &file.name {
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);
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) {
113                     let mut correct = path.to_path_buf();
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),
120                         |build| {
121                             let mut lint =
122                                 build.build(&format!("`mod.rs` files are required, found `{}`", path.display()));
123                             lint.help(&format!("move `{}` to `{}`", path.display(), correct.display(),));
124                             lint.emit();
125                         },
126                     );
127                 }
128             }
129         }
130     }
131 }
132
133 /// For each `path` we add each folder component to `folder_segments` and if the file name
134 /// is `mod.rs` we add it's parent folder to `mod_folders`.
135 fn process_paths_for_mod_files<'a>(
136     path: &'a Path,
137     folder_segments: &mut FxHashSet<&'a OsStr>,
138     mod_folders: &mut FxHashSet<&'a OsStr>,
139 ) {
140     let mut comp = path.components().rev().peekable();
141     let _ = comp.next();
142     if path.ends_with("mod.rs") {
143         mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default());
144     }
145     let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None });
146     folder_segments.extend(folders);
147 }
148
149 /// Checks every path for the presence of `mod.rs` files and emits the lint if found.
150 fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
151     if path.ends_with("mod.rs") {
152         let mut mod_file = path.to_path_buf();
153         mod_file.pop();
154         mod_file.set_extension("rs");
155
156         cx.struct_span_lint(
157             MOD_MODULE_FILES,
158             Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None),
159             |build| {
160                 let mut lint = build.build(&format!("`mod.rs` files are not allowed, found `{}`", path.display()));
161                 lint.help(&format!("move `{}` to `{}`", path.display(), mod_file.display(),));
162                 lint.emit();
163             },
164         );
165     }
166 }