stern4rust/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::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}