1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use rustc_lint::{LateContext, LateLintPass, LintStore};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use crate::common::{DefaultState, resolved_state};
mod cfg_test;
mod config;
mod paths;
mod scan;
use config::InlineTestFootprint;
declare_tool_lint! {
/// ### What it does
///
/// Caps how much inline unit-test code a production file may carry.
/// Inline test code — `#[cfg(test)] mod X { ... }` blocks, `#[test]
/// fn`s, `#[cfg(test)] fn` helpers, and any other `#[cfg(test)]`
/// item — is summed per file. The default `external_when_long` style
/// flags a file once its inline-test footprint crosses
/// `inline_max_lines` (or the optional
/// `inline_max_fraction_of_file`); `external_only` flags every inline
/// test item regardless of length. A file whose top-level items are
/// *entirely* test code is exempt — it is itself a valid extraction
/// target.
///
/// An external `#[cfg(test)] mod <name>;` (its body in a separate
/// file) is neutral: it is already extracted, so it neither charges
/// the footprint nor is otherwise checked. A test module gated by a
/// compound predicate that still implies test —
/// `#[cfg(all(test, unix))]`, `#[cfg(all(test, feature = "..."))]` —
/// is recognised the same as a bare `#[cfg(test)]`, so its external
/// file is not re-flagged as inline test code in a production file.
///
/// Only the library or binary crate is checked. Integration tests
/// (`tests/`), benchmarks (`benches/`), and examples (`examples/`)
/// are separate targets, not the library or binary whose unit-test
/// footprint this rule governs; for those compiled under `cfg(test)`
/// their top-level `#[test]` functions *are* the target rather than
/// unit tests misplaced in a production file, so they are left
/// untouched.
///
/// ### Why restrict this?
///
/// This is a stylistic preference, not a correctness issue. Both
/// source projects keep large test suites out of the production
/// file, so the file an editor tab, a `grep` hit, or a diff shows
/// is production code rather than a wall of fixtures. The threshold
/// is deliberately configurable because the exact budget varies by
/// project.
///
/// ### Example
///
/// **Avoid:**
///
/// ```rust,ignore
/// // File: foo.rs
/// #[cfg(test)]
/// mod tests {
/// /* ... 200 lines of test code ... */
/// }
/// ```
///
/// **Prefer:**
///
/// ```rust,ignore
/// // File: foo.rs
/// mod tests;
/// ```
///
/// ```rust,ignore
/// // File: foo/tests.rs
/// /* ... 200 lines of test code ... */
/// ```
pub perfectionist::INLINE_TEST_FOOTPRINT,
Warn,
"inline test code should be extracted to a separate file",
report_in_external_macro: false
}
const CONFIG_KEY: &str = "perfectionist::inline_test_footprint";
impl_lint_pass!(InlineTestFootprint => [INLINE_TEST_FOOTPRINT]);
/// Register this rule's lint declaration. Paired with [`register_pass`];
/// see the module-level convention documented in `register_lints`.
pub fn register_lint(lint_store: &mut LintStore) {
lint_store.register_lints(&[INLINE_TEST_FOOTPRINT]);
}
/// Install this rule's late pass.
///
/// A late pass is required because the only reliable way to tell that
/// an item carries `#[cfg(test)]` is `clippy_utils::is_cfg_test`,
/// which reads the `CfgTrace` attribute rustc leaves behind after
/// configuration — information that needs `TyCtxt` and is unavailable
/// to the pre-/post-expansion AST passes (the raw `#[cfg(test)]`
/// attribute is consumed during configuration). Consequently the rule
/// only sees test code in a build where `cfg(test)` is active, i.e.
/// the unit-test target that `cargo dylint -- --all-targets` checks.
pub fn register_pass(lint_store: &mut LintStore) {
if let DefaultState::Inactive = resolved_state("inline_test_footprint", DefaultState::Active) {
return;
}
lint_store.register_late_pass(|_| Box::new(InlineTestFootprint::new()));
}
impl<'tcx> LateLintPass<'tcx> for InlineTestFootprint {
fn check_crate(&mut self, lint_context: &LateContext<'tcx>) {
scan::run(self, lint_context);
}
}