2023-11-16 12:13:24 -06:00
use clippy_utils ::diagnostics ::span_lint_and_help ;
2021-09-08 09:31:47 -05:00
use rustc_ast ::ast ;
use rustc_data_structures ::fx ::{ FxHashMap , FxHashSet } ;
use rustc_lint ::{ EarlyContext , EarlyLintPass , Level , LintContext } ;
2023-12-01 11:21:58 -06:00
use rustc_session ::impl_lint_pass ;
2023-07-02 07:35:19 -05:00
use rustc_span ::def_id ::LOCAL_CRATE ;
2022-09-21 12:02:37 -05:00
use rustc_span ::{ FileName , SourceFile , Span , SyntaxContext } ;
2022-04-07 12:39:59 -05:00
use std ::ffi ::OsStr ;
use std ::path ::{ Component , Path } ;
2021-09-08 09:31:47 -05:00
declare_clippy_lint! {
/// ### What it does
2022-04-07 12:39:59 -05:00
/// Checks that module layout uses only self named module files, bans `mod.rs` files.
2021-09-08 09:31:47 -05:00
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
///
/// ### Example
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// mod.rs
/// lib.rs
/// ```
/// Use instead:
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// stuff.rs
/// lib.rs
/// ```
2021-12-06 05:33:31 -06:00
#[ clippy::version = " 1.57.0 " ]
2021-09-08 09:31:47 -05:00
pub MOD_MODULE_FILES ,
restriction ,
" checks that module layout is consistent "
}
declare_clippy_lint! {
/// ### What it does
2022-04-07 12:39:59 -05:00
/// Checks that module layout uses only `mod.rs` files.
2021-09-08 09:31:47 -05:00
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
///
/// ### Example
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// stuff.rs
/// lib.rs
/// ```
/// Use instead:
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// mod.rs
/// lib.rs
/// ```
2021-12-06 05:33:31 -06:00
#[ clippy::version = " 1.57.0 " ]
2021-09-08 09:31:47 -05:00
pub SELF_NAMED_MODULE_FILES ,
restriction ,
" checks that module layout is consistent "
}
pub struct ModStyle ;
impl_lint_pass! ( ModStyle = > [ MOD_MODULE_FILES , SELF_NAMED_MODULE_FILES ] ) ;
impl EarlyLintPass for ModStyle {
fn check_crate ( & mut self , cx : & EarlyContext < '_ > , _ : & ast ::Crate ) {
if cx . builder . lint_level ( MOD_MODULE_FILES ) . 0 = = Level ::Allow
& & cx . builder . lint_level ( SELF_NAMED_MODULE_FILES ) . 0 = = Level ::Allow
{
return ;
}
2021-12-04 09:09:15 -06:00
let files = cx . sess ( ) . source_map ( ) . files ( ) ;
2021-09-08 09:31:47 -05:00
2023-07-17 03:19:29 -05:00
let Some ( trim_to_src ) = cx . sess ( ) . opts . working_dir . local_path ( ) else {
return ;
} ;
2021-09-08 09:31:47 -05:00
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
// `[path, to]` but not foo
let mut folder_segments = FxHashSet ::default ( ) ;
// `mod_folders` is all the unique folder names that contain a mod.rs file
let mut mod_folders = FxHashSet ::default ( ) ;
// `file_map` maps file names to the full path including the file name
// `{ foo => path/to/foo.rs, .. }
let mut file_map = FxHashMap ::default ( ) ;
for file in files . iter ( ) {
2023-07-02 07:35:19 -05:00
if let FileName ::Real ( name ) = & file . name
& & let Some ( lp ) = name . local_path ( )
& & file . cnum = = LOCAL_CRATE
{
// [#8887](https://github.com/rust-lang/rust-clippy/issues/8887)
// Only check files in the current crate.
// Fix false positive that crate dependency in workspace sub directory
// is checked unintentionally.
2022-04-07 12:39:59 -05:00
let path = if lp . is_relative ( ) {
lp
} else if let Ok ( relative ) = lp . strip_prefix ( trim_to_src ) {
relative
} else {
continue ;
} ;
if let Some ( stem ) = path . file_stem ( ) {
file_map . insert ( stem , ( file , path ) ) ;
}
process_paths_for_mod_files ( path , & mut folder_segments , & mut mod_folders ) ;
check_self_named_mod_exists ( cx , path , file ) ;
2021-09-08 09:31:47 -05:00
}
}
for folder in & folder_segments {
if ! mod_folders . contains ( folder ) {
if let Some ( ( file , path ) ) = file_map . get ( folder ) {
2022-04-07 12:39:59 -05:00
let mut correct = path . to_path_buf ( ) ;
2021-09-08 09:31:47 -05:00
correct . pop ( ) ;
correct . push ( folder ) ;
correct . push ( " mod.rs " ) ;
2023-11-16 12:13:24 -06:00
span_lint_and_help (
cx ,
2021-09-08 09:31:47 -05:00
SELF_NAMED_MODULE_FILES ,
2021-09-10 12:57:06 -05:00
Span ::new ( file . start_pos , file . start_pos , SyntaxContext ::root ( ) , None ) ,
2023-11-16 12:13:24 -06:00
& format! ( " `mod.rs` files are required, found ` {} ` " , path . display ( ) ) ,
None ,
& format! ( " move ` {} ` to ` {} ` " , path . display ( ) , correct . display ( ) , ) ,
2021-09-08 09:31:47 -05:00
) ;
}
}
}
}
}
/// For each `path` we add each folder component to `folder_segments` and if the file name
/// is `mod.rs` we add it's parent folder to `mod_folders`.
2022-04-07 12:39:59 -05:00
fn process_paths_for_mod_files < ' a > (
path : & ' a Path ,
folder_segments : & mut FxHashSet < & ' a OsStr > ,
mod_folders : & mut FxHashSet < & ' a OsStr > ,
2021-09-08 09:31:47 -05:00
) {
let mut comp = path . components ( ) . rev ( ) . peekable ( ) ;
2023-02-25 18:08:29 -06:00
let _ : Option < _ > = comp . next ( ) ;
2021-09-08 09:31:47 -05:00
if path . ends_with ( " mod.rs " ) {
2022-04-07 12:39:59 -05:00
mod_folders . insert ( comp . peek ( ) . map ( | c | c . as_os_str ( ) ) . unwrap_or_default ( ) ) ;
2021-09-08 09:31:47 -05:00
}
2022-04-07 12:39:59 -05:00
let folders = comp . filter_map ( | c | if let Component ::Normal ( s ) = c { Some ( s ) } else { None } ) ;
2021-09-08 09:31:47 -05:00
folder_segments . extend ( folders ) ;
}
/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
2023-11-16 12:13:24 -06:00
/// We should not emit a lint for test modules in the presence of `mod.rs`.
/// 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)
/// for code-sharing between tests.
2021-09-08 09:31:47 -05:00
fn check_self_named_mod_exists ( cx : & EarlyContext < '_ > , path : & Path , file : & SourceFile ) {
2023-11-16 12:13:24 -06:00
if path . ends_with ( " mod.rs " ) & & ! path . starts_with ( " tests " ) {
2021-09-08 09:31:47 -05:00
let mut mod_file = path . to_path_buf ( ) ;
mod_file . pop ( ) ;
mod_file . set_extension ( " rs " ) ;
2023-11-16 12:13:24 -06:00
span_lint_and_help (
cx ,
2021-09-08 09:31:47 -05:00
MOD_MODULE_FILES ,
2021-09-10 12:57:06 -05:00
Span ::new ( file . start_pos , file . start_pos , SyntaxContext ::root ( ) , None ) ,
2023-11-16 12:13:24 -06:00
& format! ( " `mod.rs` files are not allowed, found ` {} ` " , path . display ( ) ) ,
None ,
& format! ( " move ` {} ` to ` {} ` " , path . display ( ) , mod_file . display ( ) ) ,
2021-09-08 09:31:47 -05:00
) ;
}
}