|
1 |
| -use clippy_utils::diagnostics::span_lint_and_help; |
2 |
| -use clippy_utils::{is_from_proc_macro, is_in_cfg_test}; |
3 |
| -use rustc_hir::{HirId, ItemId, ItemKind, Mod}; |
4 |
| -use rustc_lint::{LateContext, LateLintPass, LintContext}; |
5 |
| -use rustc_middle::lint::in_external_macro; |
| 1 | +use clippy_utils::diagnostics::span_lint_hir_and_then; |
| 2 | +use clippy_utils::source::snippet_opt; |
| 3 | +use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_from_proc_macro}; |
| 4 | +use rustc_errors::{Applicability, SuggestionStyle}; |
| 5 | +use rustc_hir::{HirId, Item, ItemKind, Mod}; |
| 6 | +use rustc_lint::{LateContext, LateLintPass}; |
6 | 7 | use rustc_session::{declare_lint_pass, declare_tool_lint};
|
7 |
| -use rustc_span::{sym, Span}; |
| 8 | +use rustc_span::hygiene::AstPass; |
| 9 | +use rustc_span::{sym, ExpnKind}; |
8 | 10 |
|
9 | 11 | declare_clippy_lint! {
|
10 | 12 | /// ### What it does
|
@@ -41,46 +43,72 @@ declare_clippy_lint! {
|
41 | 43 |
|
42 | 44 | declare_lint_pass!(ItemsAfterTestModule => [ITEMS_AFTER_TEST_MODULE]);
|
43 | 45 |
|
44 |
| -impl LateLintPass<'_> for ItemsAfterTestModule { |
45 |
| - fn check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId) { |
46 |
| - let mut was_test_mod_visited = false; |
47 |
| - let mut test_mod_span: Option<Span> = None; |
| 46 | +fn cfg_test_module<'tcx>(cx: &LateContext<'tcx>, item: &Item<'tcx>) -> bool { |
| 47 | + if let ItemKind::Mod(test_mod) = item.kind |
| 48 | + && item.span.hi() == test_mod.spans.inner_span.hi() |
| 49 | + && is_cfg_test(cx.tcx, item.hir_id()) |
| 50 | + && !item.span.from_expansion() |
| 51 | + && !is_from_proc_macro(cx, item) |
| 52 | + { |
| 53 | + true |
| 54 | + } else { |
| 55 | + false |
| 56 | + } |
| 57 | +} |
48 | 58 |
|
49 |
| - let hir = cx.tcx.hir(); |
50 |
| - let items = hir.items().collect::<Vec<ItemId>>(); |
| 59 | +impl LateLintPass<'_> for ItemsAfterTestModule { |
| 60 | + fn check_mod(&mut self, cx: &LateContext<'_>, module: &Mod<'_>, _: HirId) { |
| 61 | + let mut items = module.item_ids.iter().map(|&id| cx.tcx.hir().item(id)); |
51 | 62 |
|
52 |
| - for (i, itid) in items.iter().enumerate() { |
53 |
| - let item = hir.item(*itid); |
| 63 | + let Some((mod_pos, test_mod)) = items.by_ref().enumerate().find(|(_, item)| cfg_test_module(cx, item)) else { |
| 64 | + return; |
| 65 | + }; |
54 | 66 |
|
55 |
| - if_chain! { |
56 |
| - if was_test_mod_visited; |
57 |
| - if i == (items.len() - 3 /* Weird magic number (HIR-translation behaviour) */); |
58 |
| - if cx.sess().source_map().lookup_char_pos(item.span.lo()).file.name_hash |
59 |
| - == cx.sess().source_map().lookup_char_pos(test_mod_span.unwrap().lo()).file.name_hash; // Will never fail |
60 |
| - if !matches!(item.kind, ItemKind::Mod(_)); |
61 |
| - if !is_in_cfg_test(cx.tcx, itid.hir_id()); // The item isn't in the testing module itself |
62 |
| - if !in_external_macro(cx.sess(), item.span); |
63 |
| - if !is_from_proc_macro(cx, item); |
| 67 | + let after: Vec<_> = items |
| 68 | + .filter(|item| { |
| 69 | + // Ignore the generated test main function |
| 70 | + !(item.ident.name == sym::main |
| 71 | + && item.span.ctxt().outer_expn_data().kind == ExpnKind::AstPass(AstPass::TestHarness)) |
| 72 | + }) |
| 73 | + .collect(); |
64 | 74 |
|
65 |
| - then { |
66 |
| - 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"); |
67 |
| - }}; |
| 75 | + if let Some(last) = after.last() |
| 76 | + && after.iter().all(|&item| { |
| 77 | + !matches!(item.kind, ItemKind::Mod(_)) |
| 78 | + && !item.span.from_expansion() |
| 79 | + && !is_from_proc_macro(cx, item) |
| 80 | + }) |
| 81 | + && !fulfill_or_allowed(cx, ITEMS_AFTER_TEST_MODULE, after.iter().map(|item| item.hir_id())) |
| 82 | + { |
| 83 | + let def_spans: Vec<_> = std::iter::once(test_mod.owner_id) |
| 84 | + .chain(after.iter().map(|item| item.owner_id)) |
| 85 | + .map(|id| cx.tcx.def_span(id)) |
| 86 | + .collect(); |
68 | 87 |
|
69 |
| - if let ItemKind::Mod(module) = item.kind && item.span.hi() == module.spans.inner_span.hi() { |
70 |
| - // Check that it works the same way, the only I way I've found for #10713 |
71 |
| - for attr in cx.tcx.get_attrs(item.owner_id.to_def_id(), sym::cfg) { |
72 |
| - if_chain! { |
73 |
| - if attr.has_name(sym::cfg); |
74 |
| - if let Some(mitems) = attr.meta_item_list(); |
75 |
| - if let [mitem] = &*mitems; |
76 |
| - if mitem.has_name(sym::test); |
77 |
| - then { |
78 |
| - was_test_mod_visited = true; |
79 |
| - test_mod_span = Some(item.span); |
80 |
| - } |
| 88 | + span_lint_hir_and_then( |
| 89 | + cx, |
| 90 | + ITEMS_AFTER_TEST_MODULE, |
| 91 | + test_mod.hir_id(), |
| 92 | + def_spans, |
| 93 | + "items after a test module", |
| 94 | + |diag| { |
| 95 | + if let Some(prev) = mod_pos.checked_sub(1) |
| 96 | + && let prev = cx.tcx.hir().item(module.item_ids[prev]) |
| 97 | + && let items_span = last.span.with_lo(test_mod.span.hi()) |
| 98 | + && let Some(items) = snippet_opt(cx, items_span) |
| 99 | + { |
| 100 | + diag.multipart_suggestion_with_style( |
| 101 | + "move the items to before the test module was defined", |
| 102 | + vec![ |
| 103 | + (prev.span.shrink_to_hi(), items), |
| 104 | + (items_span, String::new()) |
| 105 | + ], |
| 106 | + Applicability::MachineApplicable, |
| 107 | + SuggestionStyle::HideCodeAlways, |
| 108 | + ); |
81 | 109 | }
|
82 |
| - } |
83 |
| - } |
| 110 | + }, |
| 111 | + ); |
84 | 112 | }
|
85 | 113 | }
|
86 | 114 | }
|
0 commit comments