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
//! # Static Analysis and Code Smell Metrics
//!
//! [Static analysis](https://en.wikipedia.org/wiki/Static_program_analysis)
//! tools scan source code without executing it, flagging patterns known
//! to correlate with defects, security vulnerabilities, or
//! maintainability problems, plus the broader category of code smells —
//! structural patterns that are not necessarily bugs but tend to make
//! code harder to understand, test, or safely change. The gap this
//! module addresses is between what a tool reports and what actually
//! matters: a raw finding count conflates trivial style preferences with
//! genuine, severe risk, and it can be driven down through suppression
//! as easily as through real fixes.
//!
//! ## Formula
//!
//! ```text
//! Findings per KLOC = findings / (lines_of_code / 1000)
//!
//! Severity-weighted score = Σ (count × severity_weight)
//! Critical = 5, Major = 3, Minor = 1
//! ```
//!
//! ## Why it matters
//!
//! The value of static analysis comes not from the raw finding count but
//! from how well an organization triages severity: a small number of
//! critical findings deserves more attention than a large number of
//! trivial ones. Reporting only a total count invites exactly the wrong
//! incentive — suppressing findings (real or not) to make the number
//! smaller — while a severity-weighted score keeps a spike in trivial
//! findings from visually swamping a smaller but far more consequential
//! rise in critical ones.
//!
//! ## Example
//!
//! ```rust
//! use software_engineering::static_analysis_metrics::{
//! FindingSeverity, findings_per_kloc, severity_weighted_finding_score,
//! };
//!
//! // 45 findings across 15,000 lines of code: 3 findings per KLOC.
//! let density = findings_per_kloc(45.0, 15_000.0).unwrap();
//! assert!((density - 3.0).abs() < 1e-9);
//!
//! // A single critical finding outweighs four minor ones — the
//! // severity-weighted score is what keeps that visible.
//! let critical = severity_weighted_finding_score(&[(FindingSeverity::Critical, 1.0)]);
//! let many_minor = severity_weighted_finding_score(&[(FindingSeverity::Minor, 4.0)]);
//! assert!(critical > many_minor);
//! ```
//!
//! ## Pitfalls
//!
//! - **Treating raw finding count as the metric** — conflates trivial
//! and severe issues and is easily gamed through suppression.
//! - **Requiring the entire historical backlog resolved before any new
//! work proceeds** — usually impractical and drives suppression rather
//! than genuine fixes.
//! - **Ignoring false-positive rate** — an unmanaged noise level leads
//! teams to tune out the tool's output entirely, including real
//! findings.
//! - **Silent, undocumented suppression of legitimate findings** —
//! erodes the tool's signal and leaves no audit trail.
//! - **Treating a finding as an automatic verdict with no human review**
//! — misses context a tool cannot see.
//!
//! ## Sources
//!
//! - Chapter 4.4, Static analysis and code smell metrics.
//!
//! Topic doc: software-engineering-metrics/locales/en-001/chapters/04-04-static-analysis-and-code-smell-metrics.md
/// Severity classification for a static-analysis finding.
/// The fixed weight applied to a finding of a given severity in
/// [`severity_weighted_finding_score`]: Critical = 5.0, Major = 3.0,
/// Minor = 1.0 — the same weighting convention this crate uses for
/// escaped defects, so a spike in trivial findings can't visually swamp
/// a smaller but more consequential rise in critical ones.
///
/// # Arguments
///
/// * `severity` — the finding severity to weight.
///
/// # Returns
///
/// The fixed weight for that severity.
///
/// # Examples
///
/// ```rust
/// use software_engineering::static_analysis_metrics::{FindingSeverity, severity_weight};
///
/// assert!((severity_weight(FindingSeverity::Critical) - 5.0).abs() < 1e-9);
/// assert!((severity_weight(FindingSeverity::Major) - 3.0).abs() < 1e-9);
/// assert!((severity_weight(FindingSeverity::Minor) - 1.0).abs() < 1e-9);
/// ```
/// Findings per thousand lines of code (KLOC) — a normalized density
/// that resists the "the codebase just got bigger" confound a raw
/// finding count has.
///
/// `findings / (lines_of_code / 1000)`.
///
/// # Arguments
///
/// * `findings` — total finding count.
/// * `lines_of_code` — size of the scanned codebase, in lines.
///
/// # Returns
///
/// The finding density per KLOC, or `None` if `lines_of_code` is zero.
///
/// # Examples
///
/// ```rust
/// use software_engineering::static_analysis_metrics::findings_per_kloc;
///
/// assert_eq!(findings_per_kloc(45.0, 15_000.0), Some(3.0));
/// assert_eq!(findings_per_kloc(45.0, 0.0), None);
/// ```
/// A severity-weighted static-analysis finding score, so a spike in
/// trivial findings cannot visually swamp a smaller rise in critical
/// ones.
///
/// Sum over `counts` of `count × severity_weight(severity)`.
///
/// # Arguments
///
/// * `counts` — pairs of (severity, count) for the findings being
/// scored.
///
/// # Returns
///
/// The severity-weighted score. An empty slice sums to `0.0`.
///
/// # Examples
///
/// ```rust
/// use software_engineering::static_analysis_metrics::{FindingSeverity, severity_weighted_finding_score};
///
/// // One critical finding (weight 5) outweighs four minor ones (weight 1 each = 4).
/// let critical = severity_weighted_finding_score(&[(FindingSeverity::Critical, 1.0)]);
/// let many_minor = severity_weighted_finding_score(&[(FindingSeverity::Minor, 4.0)]);
/// assert!(critical > many_minor);
/// assert_eq!(severity_weighted_finding_score(&[]), 0.0);
/// ```