Skip to content

Commit 4e91b2e

Browse files
committed
Add lint items_after_test_module
1 parent 592ea39 commit 4e91b2e

File tree

6 files changed

+121
-0
lines changed

6 files changed

+121
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4615,6 +4615,7 @@ Released 2018-09-13
46154615
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
46164616
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
46174617
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
4618+
[`items_after_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_test_module
46184619
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
46194620
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
46204621
[`iter_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
215215
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
216216
crate::invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED_INFO,
217217
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
218+
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
218219
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
219220
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
220221
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use clippy_utils::{diagnostics::span_lint_and_help, 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;
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 {
44+
fn check_mod(&mut self, cx: &LateContext<'_>, _: &Mod<'_>, _: HirId) {
45+
let mut was_test_mod_visited = false;
46+
47+
let hir = cx.tcx.hir();
48+
let items = hir.items().collect::<Vec<ItemId>>();
49+
50+
for itid in items.iter() {
51+
let item = hir.item(*itid);
52+
53+
if_chain! {
54+
if was_test_mod_visited;
55+
if !matches!(item.kind, ItemKind::Mod(_));
56+
if !is_in_cfg_test(cx.tcx, itid.hir_id()); // The item isn't in the testing module itself
57+
58+
if !in_external_macro(cx.sess(), item.span);
59+
then {
60+
span_lint_and_help(cx, ITEMS_AFTER_TEST_MODULE, item.span, "an item was found after the testing module", None, "move the item to before the testing module was defined");
61+
}};
62+
63+
if matches!(item.kind, ItemKind::Mod(_)) {
64+
for attr in cx.tcx.get_attrs(item.owner_id.to_def_id(), sym::cfg) {
65+
if_chain! {
66+
if attr.has_name(sym::cfg);
67+
if let Some(mitems) = attr.meta_item_list();
68+
if let [mitem] = &*mitems;
69+
if mitem.has_name(sym::test);
70+
then {
71+
was_test_mod_visited = true;
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ mod int_plus_one;
159159
mod invalid_upcast_comparisons;
160160
mod invalid_utf8_in_unchecked;
161161
mod items_after_statements;
162+
mod items_after_test_module;
162163
mod iter_not_returning_iterator;
163164
mod large_const_arrays;
164165
mod large_enum_variant;
@@ -960,6 +961,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
960961
store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
961962
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
962963
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
964+
store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule));
963965
// add lints here, do not remove this comment, it's used in `new_lint`
964966
}
965967

tests/ui/items_after_test_module.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// compile-flags: --test
2+
#![allow(unused)]
3+
#![warn(clippy::items_after_test_module)]
4+
5+
fn main() {}
6+
7+
fn should_not_lint() {}
8+
9+
#[allow(dead_code)]
10+
#[allow(unused)] // Some attributes to check that span replacement is good enough
11+
#[allow(clippy::allow_attributes)]
12+
#[cfg(test)]
13+
mod tests {
14+
#[test]
15+
fn hi() {}
16+
}
17+
18+
fn should_lint() {}
19+
20+
const SHOULD_ALSO_LINT: usize = 1;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: an item was found after the testing module
2+
--> $DIR/items_after_test_module.rs:18:1
3+
|
4+
LL | fn should_lint() {}
5+
| ^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: move the item to before the testing module was defined
8+
= note: `-D clippy::items-after-test-module` implied by `-D warnings`
9+
10+
error: an item was found after the testing module
11+
--> $DIR/items_after_test_module.rs:20:1
12+
|
13+
LL | const SHOULD_ALSO_LINT: usize = 1;
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
|
16+
= help: move the item to before the testing module was defined
17+
18+
error: aborting due to 2 previous errors
19+

0 commit comments

Comments
 (0)