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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
use super::*;
#[test]
fn orphan_suppression_window_and_dimension_matching() {
// An orphan is a marker with no matching in-window finding. The marker
// (line 5) matches an SRP finding within ANNOTATION_WINDOW=3 lines that
// shares its dimension; a wrong dimension still orphans; an empty-dim
// marker acts as a wildcard (defensive — the parser no longer emits one).
// (label, dims, finding_line, expected_orphans)
use crate::findings::Dimension;
let cases: &[(&str, &[Dimension], Option<usize>, usize)] = &[
("unmatched marker, no finding", &[Dimension::Srp], None, 1),
("in-window finding matches", &[Dimension::Srp], Some(8), 0),
("dimension mismatch orphans", &[Dimension::Dry], Some(7), 1),
("empty-dim acts as wildcard", &[], Some(6), 0),
];
for (label, dims, finding_line, expected) in cases {
assert_eq!(
srp_orphan_count(dims, *finding_line),
*expected,
"case {label}"
);
}
}
/// Flag-style complexity metrics (cognitive / cyclomatic / length / nesting),
/// each over its threshold: `(label, sup_line, fn_line, metrics)`.
fn complexity_flag_cases() -> Vec<(&'static str, usize, usize, ComplexityMetrics)> {
vec![
(
"cognitive",
5,
6,
ComplexityMetrics {
cognitive_complexity: 99,
..Default::default()
},
),
(
"cyclomatic",
5,
6,
ComplexityMetrics {
cyclomatic_complexity: 99,
..Default::default()
},
),
(
"function length",
5,
6,
ComplexityMetrics {
function_lines: 200,
..Default::default()
},
),
(
"nesting",
5,
6,
ComplexityMetrics {
max_nesting: 10,
..Default::default()
},
),
]
}
/// The remaining over-threshold metric kinds: unsafe blocks, unwrap count, and
/// a magic number. `(label, sup_line, fn_line, metrics)`.
fn complexity_metric_extra_cases() -> Vec<(&'static str, usize, usize, ComplexityMetrics)> {
use crate::adapters::analyzers::iosp::MagicNumberOccurrence;
vec![
(
"unsafe block",
5,
6,
ComplexityMetrics {
unsafe_blocks: 1,
..Default::default()
},
),
(
"error handling unwrap",
5,
6,
ComplexityMetrics {
unwrap_count: 3,
..Default::default()
},
),
(
// The marker attaches to the function (line 6); the magic literal
// sits deeper in the body (line 12). The orphan position anchors at
// the function line, so a marker on the function — not on the
// literal — is the one that matches.
"magic number",
6,
6,
ComplexityMetrics {
magic_numbers: vec![MagicNumberOccurrence {
line: 12,
value: "42".into(),
}],
..Default::default()
},
),
]
}
#[test]
fn suppressed_complexity_metric_over_threshold_is_not_orphan() {
// A `qual:allow(complexity)` marker clears the matching *_warning flag, but
// the orphan detector reads RAW metrics against config thresholds — when the
// raw metric still exceeds threshold the marker matches a real (suppressed)
// finding and must NOT be flagged orphan. Covers every complexity metric kind.
let mut cases = complexity_flag_cases();
cases.extend(complexity_metric_extra_cases());
for (label, sup_line, fn_line, metrics) in &cases {
let orphans = complexity_sup_orphans(*sup_line, *fn_line, metrics.clone());
assert!(
orphans.is_empty(),
"case {label}: marker clearing a complexity flag must not be orphan, got {orphans:?}"
);
}
}
#[test]
fn suppressed_srp_param_over_threshold_is_not_orphan() {
// A `// qual:allow(srp)` marker on a function with >5 parameters:
// `apply_parameter_warnings` now records the warning with
// suppressed=true (it used to filter them out), so the orphan
// checker finds a matching SRP position.
use crate::findings::Suppression;
let mut sups = HashMap::new();
sups.insert(
"src/x.rs".to_string(),
vec![Suppression {
line: 5,
dimensions: vec![crate::findings::Dimension::Srp],
reason: None,
target: None,
}],
);
let mut analysis = empty_analysis();
analysis
.findings
.srp
.push(make_srp_param_finding("src/x.rs", 6, true));
let orphans = crate::app::orphan_suppressions::detect_orphan_suppressions(
&sups,
&std::collections::HashMap::new(),
&analysis,
&Config::default(),
);
assert!(
orphans.is_empty(),
"SRP param marker must match even on suppressed warnings, got: {orphans:?}"
);
}
#[test]
fn coupling_marker_is_not_orphan_for_structural_coupling_finding() {
// Structural binary checks (OI, SIT, DEH, IET) carry
// `dimension == Coupling` and are line-anchored — a 5-line
// coupling-marker window DOES suppress them. The orphan
// checker must treat coupling-only markers as verifiable when a
// line-anchored coupling position is available in the file.
use crate::findings::Suppression;
let mut sups = HashMap::new();
sups.insert(
"src/foo.rs".to_string(),
vec![Suppression {
line: 10,
dimensions: vec![crate::findings::Dimension::Coupling],
reason: None,
target: None,
}],
);
let mut analysis = empty_analysis();
analysis
.findings
.coupling
.push(make_structural_coupling_finding("src/foo.rs", 12));
let orphans = crate::app::orphan_suppressions::detect_orphan_suppressions(
&sups,
&std::collections::HashMap::new(),
&analysis,
&Config::default(),
);
assert!(
orphans.is_empty(),
"coupling marker for a line-anchored structural finding must not be orphan, got: {orphans:?}"
);
}
#[test]
fn coupling_only_marker_with_no_line_anchored_finding_is_skipped() {
// When the file has no line-anchored Coupling position, a
// coupling-only marker is unverifiable (pure module-level
// coupling is global). We skip it rather than emit a
// potentially-false orphan.
use crate::findings::Suppression;
let mut sups = HashMap::new();
sups.insert(
"src/foo.rs".to_string(),
vec![Suppression {
line: 5,
dimensions: vec![crate::findings::Dimension::Coupling],
reason: None,
target: None,
}],
);
let analysis = empty_analysis();
let orphans = crate::app::orphan_suppressions::detect_orphan_suppressions(
&sups,
&std::collections::HashMap::new(),
&analysis,
&Config::default(),
);
assert!(
orphans.is_empty(),
"coupling-only marker without a line-anchored Coupling finding must be skipped, got: {orphans:?}"
);
}