1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
- use rustc_ast:: ast;
3
- use rustc_data_structures:: fx:: { FxHashMap , FxHashSet , FxIndexSet } ;
2
+ use rustc_ast:: ast:: { self , Inline , ItemKind , ModKind } ;
4
3
use rustc_lint:: { EarlyContext , EarlyLintPass , Level , LintContext } ;
5
4
use rustc_session:: impl_lint_pass;
6
5
use rustc_span:: def_id:: LOCAL_CRATE ;
7
- use rustc_span:: { FileName , SourceFile , Span , SyntaxContext } ;
8
- use std:: ffi :: OsStr ;
9
- use std:: path :: { Component , Path } ;
6
+ use rustc_span:: { FileName , SourceFile , Span , SyntaxContext , sym } ;
7
+ use std:: path :: { Path , PathBuf } ;
8
+ use std:: sync :: Arc ;
10
9
11
10
declare_clippy_lint ! {
12
11
/// ### What it does
@@ -60,107 +59,97 @@ declare_clippy_lint! {
60
59
/// mod.rs
61
60
/// lib.rs
62
61
/// ```
63
-
64
62
#[ clippy:: version = "1.57.0" ]
65
63
pub SELF_NAMED_MODULE_FILES ,
66
64
restriction,
67
65
"checks that module layout is consistent"
68
66
}
69
67
70
- pub struct ModStyle ;
71
-
72
68
impl_lint_pass ! ( ModStyle => [ MOD_MODULE_FILES , SELF_NAMED_MODULE_FILES ] ) ;
73
69
70
+ pub struct ModState {
71
+ contains_external : bool ,
72
+ has_path_attr : bool ,
73
+ mod_file : Arc < SourceFile > ,
74
+ }
75
+
76
+ #[ derive( Default ) ]
77
+ pub struct ModStyle {
78
+ working_dir : Option < PathBuf > ,
79
+ module_stack : Vec < ModState > ,
80
+ }
81
+
74
82
impl EarlyLintPass for ModStyle {
75
83
fn check_crate ( & mut self , cx : & EarlyContext < ' _ > , _: & ast:: Crate ) {
84
+ self . working_dir = cx. sess ( ) . opts . working_dir . local_path ( ) . map ( Path :: to_path_buf) ;
85
+ }
86
+
87
+ fn check_item ( & mut self , cx : & EarlyContext < ' _ > , item : & ast:: Item ) {
76
88
if cx. builder . lint_level ( MOD_MODULE_FILES ) . level == Level :: Allow
77
89
&& cx. builder . lint_level ( SELF_NAMED_MODULE_FILES ) . level == Level :: Allow
78
90
{
79
91
return ;
80
92
}
93
+ if let ItemKind :: Mod ( .., ModKind :: Loaded ( _, Inline :: No { .. } , mod_spans, ..) ) = & item. kind {
94
+ let has_path_attr = item. attrs . iter ( ) . any ( |attr| attr. has_name ( sym:: path) ) ;
95
+ if !has_path_attr && let Some ( current) = self . module_stack . last_mut ( ) {
96
+ current. contains_external = true ;
97
+ }
98
+ let mod_file = cx. sess ( ) . source_map ( ) . lookup_source_file ( mod_spans. inner_span . lo ( ) ) ;
99
+ self . module_stack . push ( ModState {
100
+ contains_external : false ,
101
+ has_path_attr,
102
+ mod_file,
103
+ } ) ;
104
+ }
105
+ }
81
106
82
- let files = cx. sess ( ) . source_map ( ) . files ( ) ;
83
-
84
- let Some ( trim_to_src) = cx. sess ( ) . opts . working_dir . local_path ( ) else {
107
+ fn check_item_post ( & mut self , cx : & EarlyContext < ' _ > , item : & ast:: Item ) {
108
+ if cx. builder . lint_level ( MOD_MODULE_FILES ) . level == Level :: Allow
109
+ && cx. builder . lint_level ( SELF_NAMED_MODULE_FILES ) . level == Level :: Allow
110
+ {
85
111
return ;
86
- } ;
87
-
88
- // `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
89
- // `[path, to]` but not foo
90
- let mut folder_segments = FxIndexSet :: default ( ) ;
91
- // `mod_folders` is all the unique folder names that contain a mod.rs file
92
- let mut mod_folders = FxHashSet :: default ( ) ;
93
- // `file_map` maps file names to the full path including the file name
94
- // `{ foo => path/to/foo.rs, .. }
95
- let mut file_map = FxHashMap :: default ( ) ;
96
- for file in files. iter ( ) {
97
- if let FileName :: Real ( name) = & file. name
98
- && let Some ( lp) = name. local_path ( )
99
- && file. cnum == LOCAL_CRATE
100
- {
101
- // [#8887](https://github.com/rust-lang/rust-clippy/issues/8887)
102
- // Only check files in the current crate.
103
- // Fix false positive that crate dependency in workspace sub directory
104
- // is checked unintentionally.
105
- let path = if lp. is_relative ( ) {
106
- lp
107
- } else if let Ok ( relative) = lp. strip_prefix ( trim_to_src) {
108
- relative
109
- } else {
110
- continue ;
111
- } ;
112
-
113
- if let Some ( stem) = path. file_stem ( ) {
114
- file_map. insert ( stem, ( file, path) ) ;
115
- }
116
- process_paths_for_mod_files ( path, & mut folder_segments, & mut mod_folders) ;
117
- check_self_named_mod_exists ( cx, path, file) ;
118
- }
119
112
}
120
113
121
- for folder in & folder_segments {
122
- if !mod_folders. contains ( folder)
123
- && let Some ( ( file, path) ) = file_map. get ( folder)
124
- {
125
- span_lint_and_then (
126
- cx,
127
- SELF_NAMED_MODULE_FILES ,
128
- Span :: new ( file. start_pos , file. start_pos , SyntaxContext :: root ( ) , None ) ,
129
- format ! ( "`mod.rs` files are required, found `{}`" , path. display( ) ) ,
130
- |diag| {
131
- let mut correct = path. to_path_buf ( ) ;
132
- correct. pop ( ) ;
133
- correct. push ( folder) ;
134
- correct. push ( "mod.rs" ) ;
135
- diag. help ( format ! ( "move `{}` to `{}`" , path. display( ) , correct. display( ) , ) ) ;
136
- } ,
137
- ) ;
114
+ if let ItemKind :: Mod ( .., ModKind :: Loaded ( _, Inline :: No { .. } , ..) ) = & item. kind
115
+ && let Some ( current) = self . module_stack . pop ( )
116
+ && !current. has_path_attr
117
+ {
118
+ let Some ( path) = self
119
+ . working_dir
120
+ . as_ref ( )
121
+ . and_then ( |src| try_trim_file_path_prefix ( & current. mod_file , src) )
122
+ else {
123
+ return ;
124
+ } ;
125
+ if current. contains_external {
126
+ check_self_named_module ( cx, path, & current. mod_file ) ;
138
127
}
128
+ check_mod_module ( cx, path, & current. mod_file ) ;
139
129
}
140
130
}
141
131
}
142
132
143
- /// For each `path` we add each folder component to `folder_segments` and if the file name
144
- /// is `mod.rs` we add it's parent folder to `mod_folders`.
145
- fn process_paths_for_mod_files < ' a > (
146
- path : & ' a Path ,
147
- folder_segments : & mut FxIndexSet < & ' a OsStr > ,
148
- mod_folders : & mut FxHashSet < & ' a OsStr > ,
149
- ) {
150
- let mut comp = path. components ( ) . rev ( ) . peekable ( ) ;
151
- let _: Option < _ > = comp. next ( ) ;
152
- if path. ends_with ( "mod.rs" ) {
153
- mod_folders. insert ( comp. peek ( ) . map ( |c| c. as_os_str ( ) ) . unwrap_or_default ( ) ) ;
133
+ fn check_self_named_module ( cx : & EarlyContext < ' _ > , path : & Path , file : & SourceFile ) {
134
+ if !path. ends_with ( "mod.rs" ) {
135
+ let mut mod_folder = path. with_extension ( "" ) ;
136
+ span_lint_and_then (
137
+ cx,
138
+ SELF_NAMED_MODULE_FILES ,
139
+ Span :: new ( file. start_pos , file. start_pos , SyntaxContext :: root ( ) , None ) ,
140
+ format ! ( "`mod.rs` files are required, found `{}`" , path. display( ) ) ,
141
+ |diag| {
142
+ mod_folder. push ( "mod.rs" ) ;
143
+ diag. help ( format ! ( "move `{}` to `{}`" , path. display( ) , mod_folder. display( ) ) ) ;
144
+ } ,
145
+ ) ;
154
146
}
155
- let folders = comp. filter_map ( |c| if let Component :: Normal ( s) = c { Some ( s) } else { None } ) ;
156
- folder_segments. extend ( folders) ;
157
147
}
158
148
159
- /// Checks every path for the presence of `mod.rs` files and emits the lint if found.
160
149
/// We should not emit a lint for test modules in the presence of `mod.rs`.
161
150
/// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test)
162
151
/// for code-sharing between tests.
163
- fn check_self_named_mod_exists ( cx : & EarlyContext < ' _ > , path : & Path , file : & SourceFile ) {
152
+ fn check_mod_module ( cx : & EarlyContext < ' _ > , path : & Path , file : & SourceFile ) {
164
153
if path. ends_with ( "mod.rs" ) && !path. starts_with ( "tests" ) {
165
154
span_lint_and_then (
166
155
cx,
@@ -177,3 +166,17 @@ fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &Source
177
166
) ;
178
167
}
179
168
}
169
+
170
+ fn try_trim_file_path_prefix < ' a > ( file : & ' a SourceFile , prefix : & ' a Path ) -> Option < & ' a Path > {
171
+ if let FileName :: Real ( name) = & file. name
172
+ && let Some ( mut path) = name. local_path ( )
173
+ && file. cnum == LOCAL_CRATE
174
+ {
175
+ if !path. is_relative ( ) {
176
+ path = path. strip_prefix ( prefix) . ok ( ) ?;
177
+ }
178
+ Some ( path)
179
+ } else {
180
+ None
181
+ }
182
+ }
0 commit comments