Skip to content

Commit 39150e4

Browse files
committed
Add lint items_after_test_module
1 parent ac4838c commit 39150e4

7 files changed

+147
-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: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use clippy_utils::{diagnostics::span_lint_and_then, is_in_cfg_test, source::snippet};
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{HirId, ItemId, ItemKind, Mod};
4+
use rustc_lint::{LateContext, LateLintPass, LintContext};
5+
use rustc_middle::lint::in_external_macro;
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
use rustc_span::{sym, BytePos, Span};
8+
9+
declare_clippy_lint! {
10+
/// ### What it does
11+
/// Triggers if an item is declared after the testing module marked with `#[cfg(test)]`.
12+
/// ### Why is this bad?
13+
/// Having items declared after the testing module is confusing and may lead to bad test coverage.
14+
/// ### Example
15+
/// ```rust
16+
/// #[cfg(test)]
17+
/// mod tests {
18+
/// // [...]
19+
/// }
20+
///
21+
/// fn my_function() {
22+
/// // [...]
23+
/// }
24+
/// ```
25+
/// Use instead:
26+
/// ```rust
27+
/// fn my_function() {
28+
/// // [...]
29+
/// }
30+
///
31+
/// #[cfg(test)]
32+
/// mod tests {
33+
/// // [...]
34+
/// }
35+
/// ```
36+
#[clippy::version = "1.70.0"]
37+
pub ITEMS_AFTER_TEST_MODULE,
38+
style,
39+
"An item was found after the testing module `tests`"
40+
}
41+
42+
declare_lint_pass!(ItemsAfterTestModule => [ITEMS_AFTER_TEST_MODULE]);
43+
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 when_was_visited = 0;
48+
49+
let hir = cx.tcx.hir();
50+
let items = hir.items().collect::<Vec<ItemId>>();
51+
52+
for (i, itid) in items.iter().enumerate() {
53+
let item = hir.item(*itid);
54+
55+
if_chain! {
56+
if was_test_mod_visited;
57+
if !matches!(item.kind, ItemKind::Mod(_) | ItemKind::Use(_, _));
58+
if !is_in_cfg_test(cx.tcx, itid.hir_id()); // The item isn't in the testing module itself
59+
if !in_external_macro(cx.sess(), item.span);
60+
then {
61+
was_test_mod_visited = false;
62+
span_lint_and_then(cx, ITEMS_AFTER_TEST_MODULE, item.span, "an item was found after the testing module", |diag| {
63+
diag.multipart_suggestion("move the item to before the testing module was defined", vec![
64+
(item.span, String::new()), // Remove the item
65+
(
66+
Span::new(
67+
hir.item(items[when_was_visited - 1]).span.hi() + BytePos(1),
68+
hir.item(items[when_was_visited]).span.lo() - BytePos(1),
69+
item.span.ctxt(), item.span.parent()),
70+
71+
format!("\n{}\n", snippet(cx, item.span, ".."))
72+
) // ^ Copy the item to the new location
73+
], Applicability::MachineApplicable);
74+
});
75+
}
76+
}
77+
78+
if matches!(item.kind, ItemKind::Mod(_)) {
79+
for attr in cx.tcx.get_attrs(item.owner_id.to_def_id(), sym::cfg) {
80+
if_chain! {
81+
if attr.has_name(sym::cfg);
82+
if let Some(mitems) = attr.meta_item_list();
83+
if let [mitem] = &*mitems;
84+
if mitem.has_name(sym::test);
85+
then {
86+
was_test_mod_visited = true;
87+
when_was_visited = i;
88+
}
89+
}
90+
}
91+
}
92+
}
93+
}
94+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ mod int_plus_one;
160160
mod invalid_upcast_comparisons;
161161
mod invalid_utf8_in_unchecked;
162162
mod items_after_statements;
163+
mod items_after_test_module;
163164
mod iter_not_returning_iterator;
164165
mod large_const_arrays;
165166
mod large_enum_variant;
@@ -951,6 +952,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
951952
))
952953
});
953954
store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk));
955+
store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule));
954956
// add lints here, do not remove this comment, it's used in `new_lint`
955957
}
956958

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// compile-flags: --test
2+
// run-rustfix
3+
#![allow(unused)]
4+
#![warn(clippy::items_after_test_module)]
5+
6+
fn main() {}
7+
8+
fn should_not_lint() {}
9+
10+
fn should_lint() {}
11+
12+
mod tests {
13+
#[test]
14+
fn hi() {}
15+
}
16+
17+

tests/ui/items_after_test_module.rs

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

0 commit comments

Comments
 (0)