1 use clippy_utils::{diagnostics::span_lint_and_help, is_from_proc_macro, is_in_cfg_test}; 2 use rustc_hir::{HirId, ItemId, ItemKind, Mod}; 3 use rustc_lint::{LateContext, LateLintPass, LintContext}; 4 use rustc_middle::lint::in_external_macro; 5 use rustc_session::{declare_lint_pass, declare_tool_lint}; 6 use rustc_span::{sym, Span}; 7 8 declare_clippy_lint! { 9 /// ### What it does 10 /// Triggers if an item is declared after the testing module marked with `#[cfg(test)]`. 11 /// ### Why is this bad? 12 /// Having items declared after the testing module is confusing and may lead to bad test coverage. 13 /// ### Example 14 /// ```rust 15 /// #[cfg(test)] 16 /// mod tests { 17 /// // [...] 18 /// } 19 /// 20 /// fn my_function() { 21 /// // [...] 22 /// } 23 /// ``` 24 /// Use instead: 25 /// ```rust 26 /// fn my_function() { 27 /// // [...] 28 /// } 29 /// 30 /// #[cfg(test)] 31 /// mod tests { 32 /// // [...] 33 /// } 34 /// ``` 35 #[clippy::version = "1.70.0"] 36 pub ITEMS_AFTER_TEST_MODULE, 37 style, 38 "An item was found after the testing module `tests`" 39 } 40 41 declare_lint_pass!(ItemsAfterTestModule => [ITEMS_AFTER_TEST_MODULE]); 42 43 impl LateLintPass<'_> for ItemsAfterTestModule { check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId)44 fn check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId) { 45 let mut was_test_mod_visited = false; 46 let mut test_mod_span: Option<Span> = None; 47 48 let hir = cx.tcx.hir(); 49 let items = hir.items().collect::<Vec<ItemId>>(); 50 51 for (i, itid) in items.iter().enumerate() { 52 let item = hir.item(*itid); 53 54 if_chain! { 55 if was_test_mod_visited; 56 if i == (items.len() - 3 /* Weird magic number (HIR-translation behaviour) */); 57 if cx.sess().source_map().lookup_char_pos(item.span.lo()).file.name_hash 58 == cx.sess().source_map().lookup_char_pos(test_mod_span.unwrap().lo()).file.name_hash; // Will never fail 59 if !matches!(item.kind, ItemKind::Mod(_)); 60 if !is_in_cfg_test(cx.tcx, itid.hir_id()); // The item isn't in the testing module itself 61 if !in_external_macro(cx.sess(), item.span); 62 if !is_from_proc_macro(cx, item); 63 64 then { 65 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"); 66 }}; 67 68 if let ItemKind::Mod(module) = item.kind && item.span.hi() == module.spans.inner_span.hi() { 69 // Check that it works the same way, the only I way I've found for #10713 70 for attr in cx.tcx.get_attrs(item.owner_id.to_def_id(), sym::cfg) { 71 if_chain! { 72 if attr.has_name(sym::cfg); 73 if let Some(mitems) = attr.meta_item_list(); 74 if let [mitem] = &*mitems; 75 if mitem.has_name(sym::test); 76 then { 77 was_test_mod_visited = true; 78 test_mod_span = Some(item.span); 79 } 80 } 81 } 82 } 83 } 84 } 85 } 86