Skip to content

Commit 94a000b

Browse files
committed
Auto merge of rust-lang#11293 - mrnossiom:11234, r=Jarcho
feat: add cfg_not_test lint <!-- - \[x] Followed [lint naming conventions][lint_naming] - \[x] Added passing UI tests (including committed `.stderr` file) - \[x] `cargo test` passes locally - \[x] Executed `cargo dev update_lints` - \[ ] Added lint documentation - \[x] Run `cargo dev fmt` [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints --> Fixes rust-lang#11234 changelog: new lint: [`cfg_not_test`] I don't know whether to lint only the `attr` or also the item associated to it. I guess this would mean putting the check in another place than `check_attribute` but I can't find a way to get the associated item to the attribute. Also, I'm not sure how to document this lint, I feel like my explications are bad.
2 parents 3ef3667 + dd37441 commit 94a000b

File tree

6 files changed

+141
-0
lines changed

6 files changed

+141
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5254,6 +5254,7 @@ Released 2018-09-13
52545254
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
52555255
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
52565256
[`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts
5257+
[`cfg_not_test`]: https://rust-lang.github.io/rust-clippy/master/index.html#cfg_not_test
52575258
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
52585259
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
52595260
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp

clippy_lints/src/cfg_not_test.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use rustc_ast::NestedMetaItem;
3+
use rustc_lint::{EarlyContext, EarlyLintPass};
4+
use rustc_session::declare_lint_pass;
5+
6+
declare_clippy_lint! {
7+
/// ### What it does
8+
/// Checks for usage of `cfg` that excludes code from `test` builds. (i.e., `#{cfg(not(test))]`)
9+
///
10+
/// ### Why is this bad?
11+
/// This may give the false impression that a codebase has 100% coverage, yet actually has untested code.
12+
/// Enabling this also guards against excessive mockery as well, which is an anti-pattern.
13+
///
14+
/// ### Example
15+
/// ```rust
16+
/// # fn important_check() {}
17+
/// #[cfg(not(test))]
18+
/// important_check(); // I'm not actually tested, but not including me will falsely increase coverage!
19+
/// ```
20+
/// Use instead:
21+
/// ```rust
22+
/// # fn important_check() {}
23+
/// important_check();
24+
/// ```
25+
#[clippy::version = "1.73.0"]
26+
pub CFG_NOT_TEST,
27+
restriction,
28+
"enforce against excluding code from test builds"
29+
}
30+
31+
declare_lint_pass!(CfgNotTest => [CFG_NOT_TEST]);
32+
33+
impl EarlyLintPass for CfgNotTest {
34+
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &rustc_ast::Attribute) {
35+
if attr.has_name(rustc_span::sym::cfg) && contains_not_test(attr.meta_item_list().as_deref(), false) {
36+
span_lint_and_then(
37+
cx,
38+
CFG_NOT_TEST,
39+
attr.span,
40+
"code is excluded from test builds",
41+
|diag| {
42+
diag.help("consider not excluding any code from test builds");
43+
diag.note_once("this could increase code coverage despite not actually being tested");
44+
},
45+
);
46+
}
47+
}
48+
}
49+
50+
fn contains_not_test(list: Option<&[NestedMetaItem]>, not: bool) -> bool {
51+
list.is_some_and(|list| {
52+
list.iter().any(|item| {
53+
item.ident().is_some_and(|ident| match ident.name {
54+
rustc_span::sym::not => contains_not_test(item.meta_item_list(), !not),
55+
rustc_span::sym::test => not,
56+
_ => contains_not_test(item.meta_item_list(), not),
57+
})
58+
})
59+
})
60+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
104104
crate::casts::REF_AS_PTR_INFO,
105105
crate::casts::UNNECESSARY_CAST_INFO,
106106
crate::casts::ZERO_PTR_INFO,
107+
crate::cfg_not_test::CFG_NOT_TEST_INFO,
107108
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
108109
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
109110
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ mod box_default;
9292
mod byte_char_slices;
9393
mod cargo;
9494
mod casts;
95+
mod cfg_not_test;
9596
mod checked_conversions;
9697
mod cognitive_complexity;
9798
mod collapsible_if;
@@ -1176,6 +1177,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
11761177
store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers));
11771178
store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains));
11781179
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
1180+
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
11791181
// add lints here, do not remove this comment, it's used in `new_lint`
11801182
}
11811183

tests/ui/cfg_not_test.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![allow(unused)]
2+
#![warn(clippy::cfg_not_test)]
3+
4+
fn important_check() {}
5+
6+
fn main() {
7+
// Statement
8+
#[cfg(not(test))]
9+
let answer = 42;
10+
11+
// Expression
12+
#[cfg(not(test))]
13+
important_check();
14+
15+
// Make sure only not(test) are checked, not other attributes
16+
#[cfg(not(foo))]
17+
important_check();
18+
}
19+
20+
#[cfg(not(not(test)))]
21+
struct CfgNotTest;
22+
23+
// Deeply nested `not(test)`
24+
#[cfg(not(test))]
25+
fn foo() {}
26+
#[cfg(all(debug_assertions, not(test)))]
27+
fn bar() {}
28+
#[cfg(not(any(not(debug_assertions), test)))]
29+
fn baz() {}
30+
31+
#[cfg(test)]
32+
mod tests {}

tests/ui/cfg_not_test.stderr

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
error: code is excluded from test builds
2+
--> tests/ui/cfg_not_test.rs:8:5
3+
|
4+
LL | #[cfg(not(test))]
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= help: consider not excluding any code from test builds
8+
= note: this could increase code coverage despite not actually being tested
9+
= note: `-D clippy::cfg-not-test` implied by `-D warnings`
10+
= help: to override `-D warnings` add `#[allow(clippy::cfg_not_test)]`
11+
12+
error: code is excluded from test builds
13+
--> tests/ui/cfg_not_test.rs:12:5
14+
|
15+
LL | #[cfg(not(test))]
16+
| ^^^^^^^^^^^^^^^^^
17+
|
18+
= help: consider not excluding any code from test builds
19+
20+
error: code is excluded from test builds
21+
--> tests/ui/cfg_not_test.rs:24:1
22+
|
23+
LL | #[cfg(not(test))]
24+
| ^^^^^^^^^^^^^^^^^
25+
|
26+
= help: consider not excluding any code from test builds
27+
28+
error: code is excluded from test builds
29+
--> tests/ui/cfg_not_test.rs:26:1
30+
|
31+
LL | #[cfg(all(debug_assertions, not(test)))]
32+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
|
34+
= help: consider not excluding any code from test builds
35+
36+
error: code is excluded from test builds
37+
--> tests/ui/cfg_not_test.rs:28:1
38+
|
39+
LL | #[cfg(not(any(not(debug_assertions), test)))]
40+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41+
|
42+
= help: consider not excluding any code from test builds
43+
44+
error: aborting due to 5 previous errors
45+

0 commit comments

Comments
 (0)