mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-24 05:33:27 +00:00
Auto merge of #11611 - Alexendoo:items-after-test-module-check-crate, r=blyxyas
Fix `items_after_test_module` for non root modules, add applicable suggestion Fixes #11050 Fixes #11153 changelog: [`items_after_test_module`]: Now suggests a machine-applicable suggestion. changelog: [`items:after_test_module`]: Also lints for non root modules
This commit is contained in:
commit
279127ce2e
11 changed files with 177 additions and 59 deletions
|
@ -1,10 +1,12 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_help;
|
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
||||||
use clippy_utils::{is_from_proc_macro, is_in_cfg_test};
|
use clippy_utils::source::snippet_opt;
|
||||||
use rustc_hir::{HirId, ItemId, ItemKind, Mod};
|
use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_from_proc_macro};
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
use rustc_errors::{Applicability, SuggestionStyle};
|
||||||
use rustc_middle::lint::in_external_macro;
|
use rustc_hir::{HirId, Item, ItemKind, Mod};
|
||||||
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
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! {
|
declare_clippy_lint! {
|
||||||
/// ### What it does
|
/// ### What it does
|
||||||
|
@ -41,46 +43,72 @@ declare_clippy_lint! {
|
||||||
|
|
||||||
declare_lint_pass!(ItemsAfterTestModule => [ITEMS_AFTER_TEST_MODULE]);
|
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 {
|
impl LateLintPass<'_> for ItemsAfterTestModule {
|
||||||
fn check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId) {
|
fn check_mod(&mut self, cx: &LateContext<'_>, module: &Mod<'_>, _: HirId) {
|
||||||
let mut was_test_mod_visited = false;
|
let mut items = module.item_ids.iter().map(|&id| cx.tcx.hir().item(id));
|
||||||
let mut test_mod_span: Option<Span> = None;
|
|
||||||
|
|
||||||
let hir = cx.tcx.hir();
|
let Some((mod_pos, test_mod)) = items.by_ref().enumerate().find(|(_, item)| cfg_test_module(cx, item)) else {
|
||||||
let items = hir.items().collect::<Vec<ItemId>>();
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
for (i, itid) in items.iter().enumerate() {
|
let after: Vec<_> = items
|
||||||
let item = hir.item(*itid);
|
.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 let Some(last) = after.last()
|
||||||
if was_test_mod_visited;
|
&& after.iter().all(|&item| {
|
||||||
if i == (items.len() - 3 /* Weird magic number (HIR-translation behaviour) */);
|
!matches!(item.kind, ItemKind::Mod(_))
|
||||||
if cx.sess().source_map().lookup_char_pos(item.span.lo()).file.name_hash
|
&& !item.span.from_expansion()
|
||||||
== cx.sess().source_map().lookup_char_pos(test_mod_span.unwrap().lo()).file.name_hash; // Will never fail
|
&& !is_from_proc_macro(cx, item)
|
||||||
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
|
&& !fulfill_or_allowed(cx, ITEMS_AFTER_TEST_MODULE, after.iter().map(|item| item.hir_id()))
|
||||||
if !in_external_macro(cx.sess(), item.span);
|
{
|
||||||
if !is_from_proc_macro(cx, item);
|
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_hir_and_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");
|
cx,
|
||||||
}};
|
ITEMS_AFTER_TEST_MODULE,
|
||||||
|
test_mod.hir_id(),
|
||||||
if let ItemKind::Mod(module) = item.kind && item.span.hi() == module.spans.inner_span.hi() {
|
def_spans,
|
||||||
// Check that it works the same way, the only I way I've found for #10713
|
"items after a test module",
|
||||||
for attr in cx.tcx.get_attrs(item.owner_id.to_def_id(), sym::cfg) {
|
|diag| {
|
||||||
if_chain! {
|
if let Some(prev) = mod_pos.checked_sub(1)
|
||||||
if attr.has_name(sym::cfg);
|
&& let prev = cx.tcx.hir().item(module.item_ids[prev])
|
||||||
if let Some(mitems) = attr.meta_item_list();
|
&& let items_span = last.span.with_lo(test_mod.span.hi())
|
||||||
if let [mitem] = &*mitems;
|
&& let Some(items) = snippet_opt(cx, items_span)
|
||||||
if mitem.has_name(sym::test);
|
{
|
||||||
then {
|
diag.multipart_suggestion_with_style(
|
||||||
was_test_mod_visited = true;
|
"move the items to before the test module was defined",
|
||||||
test_mod_span = Some(item.span);
|
vec![
|
||||||
}
|
(prev.span.shrink_to_hi(), items),
|
||||||
|
(items_span, String::new())
|
||||||
|
],
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
SuggestionStyle::HideCodeAlways,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,6 @@ use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rustc_ast::ast::{self, LitKind, RangeLimits};
|
use rustc_ast::ast::{self, LitKind, RangeLimits};
|
||||||
use rustc_ast::Attribute;
|
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
use rustc_data_structures::unhash::UnhashMap;
|
use rustc_data_structures::unhash::UnhashMap;
|
||||||
use rustc_hir::def::{DefKind, Res};
|
use rustc_hir::def::{DefKind, Res};
|
||||||
|
@ -2452,11 +2451,12 @@ pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the item containing the given `HirId` has `#[cfg(test)]` attribute applied
|
/// Checks if `id` has a `#[cfg(test)]` attribute applied
|
||||||
///
|
///
|
||||||
/// Note: Add `//@compile-flags: --test` to UI tests with a `#[cfg(test)]` function
|
/// This only checks directly applied attributes, to see if a node is inside a `#[cfg(test)]` parent
|
||||||
pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
/// use [`is_in_cfg_test`]
|
||||||
fn is_cfg_test(attr: &Attribute) -> bool {
|
pub fn is_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||||
|
tcx.hir().attrs(id).iter().any(|attr| {
|
||||||
if attr.has_name(sym::cfg)
|
if attr.has_name(sym::cfg)
|
||||||
&& let Some(items) = attr.meta_item_list()
|
&& let Some(items) = attr.meta_item_list()
|
||||||
&& let [item] = &*items
|
&& let [item] = &*items
|
||||||
|
@ -2466,11 +2466,14 @@ pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if any parent node of `HirId` has `#[cfg(test)]` attribute applied
|
||||||
|
pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||||
tcx.hir()
|
tcx.hir()
|
||||||
.parent_iter(id)
|
.parent_id_iter(id)
|
||||||
.flat_map(|(parent_id, _)| tcx.hir().attrs(parent_id))
|
.any(|parent_id| is_cfg_test(tcx, parent_id))
|
||||||
.any(is_cfg_test)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied.
|
/// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied.
|
||||||
|
|
11
tests/ui/items_after_test_module/after_proc_macros.rs
Normal file
11
tests/ui/items_after_test_module/after_proc_macros.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//@aux-build:../auxiliary/proc_macros.rs
|
||||||
|
extern crate proc_macros;
|
||||||
|
|
||||||
|
proc_macros::with_span! {
|
||||||
|
span
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn f() {}
|
4
tests/ui/items_after_test_module/auxiliary/submodule.rs
Normal file
4
tests/ui/items_after_test_module/auxiliary/submodule.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {}
|
||||||
|
|
||||||
|
fn in_submodule() {}
|
|
@ -1,2 +0,0 @@
|
||||||
error: Option 'test' given more than once
|
|
||||||
|
|
8
tests/ui/items_after_test_module/in_submodule.rs
Normal file
8
tests/ui/items_after_test_module/in_submodule.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#[path = "auxiliary/submodule.rs"]
|
||||||
|
mod submodule;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn t() {}
|
||||||
|
}
|
14
tests/ui/items_after_test_module/in_submodule.stderr
Normal file
14
tests/ui/items_after_test_module/in_submodule.stderr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
error: items after a test module
|
||||||
|
--> $DIR/auxiliary/submodule.rs:2:1
|
||||||
|
|
|
||||||
|
LL | mod tests {}
|
||||||
|
| ^^^^^^^^^
|
||||||
|
LL |
|
||||||
|
LL | fn in_submodule() {}
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `-D clippy::items-after-test-module` implied by `-D warnings`
|
||||||
|
= help: to override `-D warnings` add `#[allow(clippy::items_after_test_module)]`
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
11
tests/ui/items_after_test_module/multiple_modules.rs
Normal file
11
tests/ui/items_after_test_module/multiple_modules.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn f() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod more_tests {
|
||||||
|
#[test]
|
||||||
|
fn g() {}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
//@compile-flags: --test
|
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
#![warn(clippy::items_after_test_module)]
|
#![warn(clippy::items_after_test_module)]
|
||||||
|
|
||||||
|
@ -6,6 +5,13 @@ fn main() {}
|
||||||
|
|
||||||
fn should_not_lint() {}
|
fn should_not_lint() {}
|
||||||
|
|
||||||
|
fn should_lint() {}
|
||||||
|
|
||||||
|
const SHOULD_ALSO_LINT: usize = 1;
|
||||||
|
macro_rules! should_lint {
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[allow(unused)] // Some attributes to check that span replacement is good enough
|
#[allow(unused)] // Some attributes to check that span replacement is good enough
|
||||||
#[allow(clippy::allow_attributes)]
|
#[allow(clippy::allow_attributes)]
|
||||||
|
@ -14,10 +20,3 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn hi() {}
|
fn hi() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_lint() {}
|
|
||||||
|
|
||||||
const SHOULD_ALSO_LINT: usize = 1;
|
|
||||||
macro_rules! should_not_lint {
|
|
||||||
() => {};
|
|
||||||
}
|
|
22
tests/ui/items_after_test_module/root_module.rs
Normal file
22
tests/ui/items_after_test_module/root_module.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#![allow(unused)]
|
||||||
|
#![warn(clippy::items_after_test_module)]
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn should_not_lint() {}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(unused)] // Some attributes to check that span replacement is good enough
|
||||||
|
#[allow(clippy::allow_attributes)]
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn hi() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_lint() {}
|
||||||
|
|
||||||
|
const SHOULD_ALSO_LINT: usize = 1;
|
||||||
|
macro_rules! should_lint {
|
||||||
|
() => {};
|
||||||
|
}
|
20
tests/ui/items_after_test_module/root_module.stderr
Normal file
20
tests/ui/items_after_test_module/root_module.stderr
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
error: items after a test module
|
||||||
|
--> $DIR/root_module.rs:12:1
|
||||||
|
|
|
||||||
|
LL | mod tests {
|
||||||
|
| ^^^^^^^^^
|
||||||
|
...
|
||||||
|
LL | fn should_lint() {}
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
LL |
|
||||||
|
LL | const SHOULD_ALSO_LINT: usize = 1;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
LL | macro_rules! should_lint {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `-D clippy::items-after-test-module` implied by `-D warnings`
|
||||||
|
= help: to override `-D warnings` add `#[allow(clippy::items_after_test_module)]`
|
||||||
|
= help: move the items to before the test module was defined
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
Loading…
Reference in a new issue