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))
});
}
}