xbp-analysis 10.57.0

Language-agnostic static analysis (error-handling / Aspirator-style) for XBP
Documentation
//! Analysis report aggregate.

use super::types::{Finding, LanguageId, Severity};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AnalysisReport {
    pub findings: Vec<Finding>,
    pub files_analyzed: usize,
    pub languages: Vec<LanguageId>,
    pub duration_ms: u64,
    pub skipped_rules: Vec<SkippedRule>,
    pub stats: ReportStats,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SkippedRule {
    pub rule_id: String,
    pub reason: String,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReportStats {
    pub by_severity: BTreeMap<String, usize>,
    pub by_rule: BTreeMap<String, usize>,
    pub definite_bugs: usize,
    pub review_required: usize,
}

impl AnalysisReport {
    pub fn recompute_stats(&mut self) {
        let mut by_severity = BTreeMap::new();
        let mut by_rule = BTreeMap::new();
        let mut definite_bugs = 0;
        let mut review_required = 0;
        for f in &self.findings {
            *by_severity
                .entry(f.severity.as_str().to_string())
                .or_insert(0) += 1;
            *by_rule.entry(f.rule_id.clone()).or_insert(0) += 1;
            if f.definite_bug {
                definite_bugs += 1;
            } else {
                review_required += 1;
            }
        }
        self.stats = ReportStats {
            by_severity,
            by_rule,
            definite_bugs,
            review_required,
        };
    }

    pub fn filter_min_severity(&mut self, min: Severity) {
        self.findings.retain(|f| f.severity >= min);
        self.recompute_stats();
    }

    pub fn sort_stable(&mut self) {
        self.findings.sort_by(|a, b| {
            a.location
                .file
                .cmp(&b.location.file)
                .then(a.location.start_line.cmp(&b.location.start_line))
                .then(a.location.start_column.cmp(&b.location.start_column))
                .then(a.rule_id.cmp(&b.rule_id))
                .then(a.id.cmp(&b.id))
        });
    }
}