Fix items_after_test_module for non root modules, add applicable suggestion
This commit is contained in:
parent
331d01e2bf
commit
dcc400191e
11 changed files with 177 additions and 59 deletions
|
|
@ -1,10 +1,12 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::{is_from_proc_macro, 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 clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_from_proc_macro};
|
||||
use rustc_errors::{Applicability, SuggestionStyle};
|
||||
use rustc_hir::{HirId, Item, ItemKind, Mod};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_span::hygiene::AstPass;
|
||||
use rustc_span::{sym, ExpnKind};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -41,46 +43,72 @@ declare_clippy_lint! {
|
|||
|
||||
declare_lint_pass!(ItemsAfterTestModule => [ITEMS_AFTER_TEST_MODULE]);
|
||||
|
||||
fn cfg_test_module<'tcx>(cx: &LateContext<'tcx>, item: &Item<'tcx>) -> bool {
|
||||
if let ItemKind::Mod(test_mod) = item.kind
|
||||
&& item.span.hi() == test_mod.spans.inner_span.hi()
|
||||
&& is_cfg_test(cx.tcx, item.hir_id())
|
||||
&& !item.span.from_expansion()
|
||||
&& !is_from_proc_macro(cx, item)
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for ItemsAfterTestModule {
|
||||
fn check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId) {
|
||||
let mut was_test_mod_visited = false;
|
||||
let mut test_mod_span: Option<Span> = None;
|
||||
fn check_mod(&mut self, cx: &LateContext<'_>, module: &Mod<'_>, _: HirId) {
|
||||
let mut items = module.item_ids.iter().map(|&id| cx.tcx.hir().item(id));
|
||||
|
||||
let hir = cx.tcx.hir();
|
||||
let items = hir.items().collect::<Vec<ItemId>>();
|
||||
let Some((mod_pos, test_mod)) = items.by_ref().enumerate().find(|(_, item)| cfg_test_module(cx, item)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (i, itid) in items.iter().enumerate() {
|
||||
let item = hir.item(*itid);
|
||||
let after: Vec<_> = items
|
||||
.filter(|item| {
|
||||
// Ignore the generated test main function
|
||||
!(item.ident.name == sym::main
|
||||
&& item.span.ctxt().outer_expn_data().kind == ExpnKind::AstPass(AstPass::TestHarness))
|
||||
})
|
||||
.collect();
|
||||
|
||||
if_chain! {
|
||||
if was_test_mod_visited;
|
||||
if i == (items.len() - 3 /* Weird magic number (HIR-translation behaviour) */);
|
||||
if cx.sess().source_map().lookup_char_pos(item.span.lo()).file.name_hash
|
||||
== cx.sess().source_map().lookup_char_pos(test_mod_span.unwrap().lo()).file.name_hash; // Will never fail
|
||||
if !matches!(item.kind, ItemKind::Mod(_));
|
||||
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);
|
||||
if !is_from_proc_macro(cx, item);
|
||||
if let Some(last) = after.last()
|
||||
&& after.iter().all(|&item| {
|
||||
!matches!(item.kind, ItemKind::Mod(_))
|
||||
&& !item.span.from_expansion()
|
||||
&& !is_from_proc_macro(cx, item)
|
||||
})
|
||||
&& !fulfill_or_allowed(cx, ITEMS_AFTER_TEST_MODULE, after.iter().map(|item| item.hir_id()))
|
||||
{
|
||||
let def_spans: Vec<_> = std::iter::once(test_mod.owner_id)
|
||||
.chain(after.iter().map(|item| item.owner_id))
|
||||
.map(|id| cx.tcx.def_span(id))
|
||||
.collect();
|
||||
|
||||
then {
|
||||
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");
|
||||
}};
|
||||
|
||||
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);
|
||||
if let Some(mitems) = attr.meta_item_list();
|
||||
if let [mitem] = &*mitems;
|
||||
if mitem.has_name(sym::test);
|
||||
then {
|
||||
was_test_mod_visited = true;
|
||||
test_mod_span = Some(item.span);
|
||||
}
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
ITEMS_AFTER_TEST_MODULE,
|
||||
test_mod.hir_id(),
|
||||
def_spans,
|
||||
"items after a test module",
|
||||
|diag| {
|
||||
if let Some(prev) = mod_pos.checked_sub(1)
|
||||
&& let prev = cx.tcx.hir().item(module.item_ids[prev])
|
||||
&& let items_span = last.span.with_lo(test_mod.span.hi())
|
||||
&& let Some(items) = snippet_opt(cx, items_span)
|
||||
{
|
||||
diag.multipart_suggestion_with_style(
|
||||
"move the items to before the test module was defined",
|
||||
vec![
|
||||
(prev.span.shrink_to_hi(), items),
|
||||
(items_span, String::new())
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
SuggestionStyle::HideCodeAlways,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue