Skip to content

Commit 0354cee

Browse files
committed
Add lint items_after_test_module
1 parent 9283497 commit 0354cee

File tree

6 files changed

+129
-0
lines changed

6 files changed

+129
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4730,6 +4730,7 @@ Released 2018-09-13
47304730
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
47314731
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
47324732
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
4733+
[`items_after_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_test_module
47334734
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
47344735
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
47354736
[`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: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
let mut test_mod_hash: Option<u128> = None;
47+
48+
let hir = cx.tcx.hir();
49+
let items = hir.items().collect::<Vec<ItemId>>();
50+
51+
for itid in &items {
52+
let item = hir.item(*itid);
53+
54+
if_chain! {
55+
if was_test_mod_visited;
56+
if cx.sess().source_map().lookup_char_pos(item.span.lo()).file.name_hash
57+
== test_mod_hash.unwrap(); // Will never fail
58+
if !matches!(item.kind, ItemKind::Mod(_) | ItemKind::Macro(_, _));
59+
if !is_in_cfg_test(cx.tcx, itid.hir_id()); // The item isn't in the testing module itself
60+
61+
if !in_external_macro(cx.sess(), item.span);
62+
then {
63+
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");
64+
}};
65+
66+
if matches!(item.kind, ItemKind::Mod(_)) {
67+
for attr in cx.tcx.get_attrs(item.owner_id.to_def_id(), sym::cfg) {
68+
if_chain! {
69+
if attr.has_name(sym::cfg);
70+
if let Some(mitems) = attr.meta_item_list();
71+
if let [mitem] = &*mitems;
72+
if mitem.has_name(sym::test);
73+
then {
74+
was_test_mod_visited = true;
75+
test_mod_hash = Some(cx.sess().source_map().lookup_char_pos(item.span.lo()).file.name_hash);
76+
}
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}

clippy_lints/src/lib.rs

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

tests/ui/items_after_test_module.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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;
21+
22+
macro_rules! should_not_lint {
23+
() => {};
24+
}
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)