| use clippy_utils::diagnostics::span_lint_and_then; | |
| use rustc_ast::attr::data_structures::CfgEntry; | |
| use rustc_ast::{AttrItemKind, EarlyParsedAttribute}; | |
| use rustc_lint::{EarlyContext, EarlyLintPass}; | |
| use rustc_session::declare_lint_pass; | |
| use rustc_span::sym; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for usage of `cfg` that excludes code from `test` builds. (i.e., `#[cfg(not(test))]`) | |
| /// | |
| /// ### Why is this bad? | |
| /// This may give the false impression that a codebase has 100% coverage, yet actually has untested code. | |
| /// Enabling this also guards against excessive mockery as well, which is an anti-pattern. | |
| /// | |
| /// ### Example | |
| /// ```rust | |
| /// # fn important_check() {} | |
| /// #[cfg(not(test))] | |
| /// important_check(); // I'm not actually tested, but not including me will falsely increase coverage! | |
| /// ``` | |
| /// Use instead: | |
| /// ```rust | |
| /// # fn important_check() {} | |
| /// important_check(); | |
| /// ``` | |
| #[clippy::version = "1.81.0"] | |
| pub CFG_NOT_TEST, | |
| restriction, | |
| "enforce against excluding code from test builds" | |
| } | |
| declare_lint_pass!(CfgNotTest => [CFG_NOT_TEST]); | |
| impl EarlyLintPass for CfgNotTest { | |
| fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &rustc_ast::Attribute) { | |
| if attr.has_name(sym::cfg_trace) { | |
| let AttrItemKind::Parsed(EarlyParsedAttribute::CfgTrace(cfg)) = &attr.get_normal_item().args else { | |
| unreachable!() | |
| }; | |
| if contains_not_test(cfg, false) { | |
| span_lint_and_then( | |
| cx, | |
| CFG_NOT_TEST, | |
| attr.span, | |
| "code is excluded from test builds", | |
| |diag| { | |
| diag.help("consider not excluding any code from test builds"); | |
| diag.note_once("this could increase code coverage despite not actually being tested"); | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| fn contains_not_test(cfg: &CfgEntry, not: bool) -> bool { | |
| match cfg { | |
| CfgEntry::All(subs, _) | CfgEntry::Any(subs, _) => subs.iter().any(|item| contains_not_test(item, not)), | |
| CfgEntry::Not(sub, _) => contains_not_test(sub, !not), | |
| CfgEntry::NameValue { name: sym::test, .. } => not, | |
| _ => false, | |
| } | |
| } |