Skip to main content

stern4rust/reporting/
offence_threshold.rs

1// Copyright 2025 Umberto Gotti <umberto.gotti@umbertogotti.dev>
2// Licensed under the MIT License
3// SPDX-License-Identifier: MIT
4
5use crate::reporting::offence::Offence;
6
7// How much of the report is printed.
8//
9// A first run against a large codebase can find a thousand offences, and a
10// thousand rows is not a report -- it is a wall that gets scrolled past. The
11// default shows the first hundred, which is roughly what somebody will act on
12// before re-running anyway.
13//
14// The cap is on what is SHOWN and never on what is counted. A summary that said
15// a hundred when the tree holds a thousand would be a quietly wrong report, and
16// it would be this tool producing it. The full total stays in the summary, the
17// omitted count is stated outright, and the exit code is decided from every
18// offence rather than from the printed ones.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct OffenceThreshold {
21    limit: usize,
22}
23
24impl OffenceThreshold {
25    pub const DEFAULT: usize = 100;
26
27    pub fn new(limit: usize) -> Self {
28        Self { limit }
29    }
30
31    pub fn limit(self) -> usize {
32        self.limit
33    }
34
35    // Zero is the escape hatch for "show me everything", not a way to silence
36    // the report. A limit of nothing would be a tool that finds problems and
37    // then refuses to say which.
38    pub fn is_unlimited(self) -> bool {
39        self.limit == 0
40    }
41
42    // The offences arrive sorted by file then line, so what survives is whole
43    // files from the top rather than a scattering across the tree. A reader
44    // fixes what is shown, re-runs, and gets the next file.
45    pub fn kept(self, offences: &[Offence]) -> &[Offence] {
46        if self.is_unlimited() {
47            return offences;
48        }
49        &offences[..self.limit.min(offences.len())]
50    }
51
52    pub fn omitted(self, offences: &[Offence]) -> usize {
53        offences.len() - self.kept(offences).len()
54    }
55}
56
57impl Default for OffenceThreshold {
58    fn default() -> Self {
59        Self::new(Self::DEFAULT)
60    }
61}