Skip to main content

xbp_analysis/domain/
report.rs

1//! Analysis report aggregate.
2
3use super::types::{Finding, LanguageId, Severity};
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize)]
8pub struct AnalysisReport {
9    pub findings: Vec<Finding>,
10    pub files_analyzed: usize,
11    pub languages: Vec<LanguageId>,
12    pub duration_ms: u64,
13    pub skipped_rules: Vec<SkippedRule>,
14    pub stats: ReportStats,
15}
16
17#[derive(Debug, Clone, Default, Serialize, Deserialize)]
18pub struct SkippedRule {
19    pub rule_id: String,
20    pub reason: String,
21}
22
23#[derive(Debug, Clone, Default, Serialize, Deserialize)]
24pub struct ReportStats {
25    pub by_severity: BTreeMap<String, usize>,
26    pub by_rule: BTreeMap<String, usize>,
27    pub definite_bugs: usize,
28    pub review_required: usize,
29}
30
31impl AnalysisReport {
32    pub fn recompute_stats(&mut self) {
33        let mut by_severity = BTreeMap::new();
34        let mut by_rule = BTreeMap::new();
35        let mut definite_bugs = 0;
36        let mut review_required = 0;
37        for f in &self.findings {
38            *by_severity
39                .entry(f.severity.as_str().to_string())
40                .or_insert(0) += 1;
41            *by_rule.entry(f.rule_id.clone()).or_insert(0) += 1;
42            if f.definite_bug {
43                definite_bugs += 1;
44            } else {
45                review_required += 1;
46            }
47        }
48        self.stats = ReportStats {
49            by_severity,
50            by_rule,
51            definite_bugs,
52            review_required,
53        };
54    }
55
56    pub fn filter_min_severity(&mut self, min: Severity) {
57        self.findings.retain(|f| f.severity >= min);
58        self.recompute_stats();
59    }
60
61    pub fn sort_stable(&mut self) {
62        self.findings.sort_by(|a, b| {
63            a.location
64                .file
65                .cmp(&b.location.file)
66                .then(a.location.start_line.cmp(&b.location.start_line))
67                .then(a.location.start_column.cmp(&b.location.start_column))
68                .then(a.rule_id.cmp(&b.rule_id))
69                .then(a.id.cmp(&b.id))
70        });
71    }
72}