pub const DEFAULT_THRESHOLD: f64 = 30.0;
#[must_use]
pub fn crap(
complexity: f64,
coverage_pct: f64,
) -> f64 {
let uncovered = 1.0 - (coverage_pct.clamp(0.0, 100.0) / 100.0);
complexity.powi(2) * uncovered.powi(3) + complexity
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Clean,
Crappy,
}
impl Severity {
#[must_use]
pub fn classify(
score: f64,
threshold: f64,
) -> Self {
if score > threshold {
Self::Crappy
} else {
Self::Clean
}
}
}
#[cfg(test)]
#[expect(
clippy::float_cmp,
reason = "CRAP formula is deterministic; exact equality is the right comparison"
)]
mod tests {
use super::*;
#[test]
fn trivial_method_scores_one() {
assert_eq!(crap(1.0, 100.0), 1.0);
}
#[test]
fn untested_complex_method_matches_published_example() {
assert_eq!(crap(6.0, 0.0), 42.0);
}
#[test]
fn full_coverage_leaves_only_linear_term() {
assert_eq!(crap(20.0, 100.0), 20.0);
assert_eq!(crap(5.0, 100.0), 5.0);
}
#[test]
fn cc_above_threshold_is_irredeemable_even_with_full_coverage() {
assert!(crap(31.0, 100.0) > DEFAULT_THRESHOLD);
assert!(crap(50.0, 100.0) > DEFAULT_THRESHOLD);
}
#[test]
fn score_is_monotonic_in_complexity_at_fixed_coverage() {
for cov in [0.0, 25.0, 50.0, 75.0, 100.0] {
let a = crap(2.0, cov);
let b = crap(5.0, cov);
let c = crap(10.0, cov);
assert!(a <= b, "monotonicity broken at cov={cov}: {a} vs {b}");
assert!(b <= c, "monotonicity broken at cov={cov}: {b} vs {c}");
}
}
#[test]
fn score_is_monotonic_nonincreasing_in_coverage_at_fixed_complexity() {
for cc in [1.0, 3.0, 10.0, 25.0] {
let mut prev = f64::INFINITY;
for cov in [0.0, 25.0, 50.0, 75.0, 100.0] {
let s = crap(cc, cov);
assert!(s <= prev, "cov↑ made score worse at cc={cc}");
prev = s;
}
}
}
#[test]
fn coverage_is_clamped() {
assert_eq!(crap(5.0, -10.0), crap(5.0, 0.0));
assert_eq!(crap(5.0, 150.0), crap(5.0, 100.0));
}
#[test]
fn severity_classifies_at_threshold_boundary() {
assert_eq!(Severity::classify(30.0, 30.0), Severity::Clean);
assert_eq!(Severity::classify(30.0001, 30.0), Severity::Crappy);
}
}