11use 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 } ;
43use rustc_lint:: { EarlyContext , EarlyLintPass , Level , LintContext } ;
54use rustc_session:: impl_lint_pass;
65use 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 ;
109
1110declare_clippy_lint ! {
1211 /// ### What it does
@@ -60,107 +59,97 @@ declare_clippy_lint! {
6059 /// mod.rs
6160 /// lib.rs
6261 /// ```
63-
6462 #[ clippy:: version = "1.57.0" ]
6563 pub SELF_NAMED_MODULE_FILES ,
6664 restriction,
6765 "checks that module layout is consistent"
6866}
6967
70- pub struct ModStyle ;
71-
7268impl_lint_pass ! ( ModStyle => [ MOD_MODULE_FILES , SELF_NAMED_MODULE_FILES ] ) ;
7369
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+
7482impl EarlyLintPass for ModStyle {
7583 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 ) {
7688 if cx. builder . lint_level ( MOD_MODULE_FILES ) . level == Level :: Allow
7789 && cx. builder . lint_level ( SELF_NAMED_MODULE_FILES ) . level == Level :: Allow
7890 {
7991 return ;
8092 }
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+ }
81106
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+ {
85111 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- }
119112 }
120113
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 ) ;
138127 }
128+ check_mod_module ( cx, path, & current. mod_file ) ;
139129 }
140130 }
141131}
142132
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+ ) ;
154146 }
155- let folders = comp. filter_map ( |c| if let Component :: Normal ( s) = c { Some ( s) } else { None } ) ;
156- folder_segments. extend ( folders) ;
157147}
158148
159- /// Checks every path for the presence of `mod.rs` files and emits the lint if found.
160149/// We should not emit a lint for test modules in the presence of `mod.rs`.
161150/// 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)
162151/// 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 ) {
164153 if path. ends_with ( "mod.rs" ) && !path. starts_with ( "tests" ) {
165154 span_lint_and_then (
166155 cx,
@@ -177,3 +166,17 @@ fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &Source
177166 ) ;
178167 }
179168}
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