2023-03-31 02:06:39 -05:00
use clippy_utils ::{ diagnostics ::span_lint_and_help , is_in_cfg_test } ;
use rustc_hir ::{ HirId , ItemId , ItemKind , Mod } ;
use rustc_lint ::{ LateContext , LateLintPass , LintContext } ;
use rustc_middle ::lint ::in_external_macro ;
use rustc_session ::{ declare_lint_pass , declare_tool_lint } ;
2023-04-12 13:17:26 -05:00
use rustc_span ::{ sym , Span } ;
2023-03-31 02:06:39 -05:00
declare_clippy_lint! {
/// ### What it does
/// Triggers if an item is declared after the testing module marked with `#[cfg(test)]`.
/// ### Why is this bad?
/// Having items declared after the testing module is confusing and may lead to bad test coverage.
/// ### Example
/// ```rust
/// #[cfg(test)]
/// mod tests {
/// // [...]
/// }
///
/// fn my_function() {
/// // [...]
/// }
/// ```
/// Use instead:
/// ```rust
/// fn my_function() {
/// // [...]
/// }
///
/// #[cfg(test)]
/// mod tests {
/// // [...]
/// }
/// ```
#[ clippy::version = " 1.70.0 " ]
pub ITEMS_AFTER_TEST_MODULE ,
style ,
" An item was found after the testing module `tests` "
}
declare_lint_pass! ( ItemsAfterTestModule = > [ ITEMS_AFTER_TEST_MODULE ] ) ;
impl LateLintPass < '_ > for ItemsAfterTestModule {
fn check_mod ( & mut self , cx : & LateContext < '_ > , _ : & Mod < '_ > , _ : HirId ) {
let mut was_test_mod_visited = false ;
2023-04-12 13:17:26 -05:00
let mut test_mod_span : Option < Span > = None ;
2023-03-31 02:06:39 -05:00
let hir = cx . tcx . hir ( ) ;
let items = hir . items ( ) . collect ::< Vec < ItemId > > ( ) ;
2023-04-12 13:17:26 -05:00
for ( i , itid ) in items . iter ( ) . enumerate ( ) {
2023-03-31 02:06:39 -05:00
let item = hir . item ( * itid ) ;
if_chain! {
if was_test_mod_visited ;
2023-04-12 13:17:26 -05:00
if i = = ( items . len ( ) - 3 /* Weird magic number (HIR-translation behaviour) */ ) ;
2023-03-31 02:06:39 -05:00
if cx . sess ( ) . source_map ( ) . lookup_char_pos ( item . span . lo ( ) ) . file . name_hash
2023-04-12 13:17:26 -05:00
= = cx . sess ( ) . source_map ( ) . lookup_char_pos ( test_mod_span . unwrap ( ) . lo ( ) ) . file . name_hash ; // Will never fail
if ! matches! ( item . kind , ItemKind ::Mod ( _ ) ) ;
2023-03-31 02:06:39 -05:00
if ! is_in_cfg_test ( cx . tcx , itid . hir_id ( ) ) ; // The item isn't in the testing module itself
if ! in_external_macro ( cx . sess ( ) , item . span ) ;
2023-04-12 13:17:26 -05:00
2023-03-31 02:06:39 -05:00
then {
2023-04-12 13:17:26 -05:00
span_lint_and_help ( cx , ITEMS_AFTER_TEST_MODULE , test_mod_span . unwrap ( ) . with_hi ( item . span . hi ( ) ) , " items were found after the testing module " , None , " move the items to before the testing module was defined " ) ;
2023-03-31 02:06:39 -05:00
} } ;
2023-04-27 09:43:51 -05:00
if let ItemKind ::Mod ( module ) = item . kind & & item . span . hi ( ) = = module . spans . inner_span . hi ( ) {
// Check that it works the same way, the only I way I've found for #10713
for attr in cx . tcx . get_attrs ( item . owner_id . to_def_id ( ) , sym ::cfg ) {
if_chain! {
if attr . has_name ( sym ::cfg ) ;
2023-04-12 13:17:26 -05:00
if let Some ( mitems ) = attr . meta_item_list ( ) ;
if let [ mitem ] = & * mitems ;
if mitem . has_name ( sym ::test ) ;
then {
2023-04-27 09:43:51 -05:00
was_test_mod_visited = true ;
test_mod_span = Some ( module . spans . inner_span . with_lo ( item . span . lo ( ) ) ) ;
2023-04-12 13:17:26 -05:00
}
}
2023-03-31 02:06:39 -05:00
}
2023-04-27 09:43:51 -05:00
}
2023-03-31 02:06:39 -05:00
}
}
}