use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use super::{AssessmentReport, Finding};
#[derive(Debug, Clone, Default)]
pub struct AnalysisContext {
pub scope: String,
pub previous_report: Option<AssessmentReport>,
}
pub trait Analyzer: Send + Sync {
fn id(&self) -> &str;
fn name(&self) -> &str;
fn categories(&self) -> &[&str];
fn analyze(&self, project: &Path, files: &[PathBuf], context: &AnalysisContext) -> Vec<Finding>;
}
pub struct AnalyzerRegistry {
analyzers: Vec<Box<dyn Analyzer>>,
}
impl AnalyzerRegistry {
pub fn new() -> Self {
Self {
analyzers: Vec::new(),
}
}
pub fn with_defaults() -> Self {
let mut reg = Self::new();
reg.register(Box::new(super::analyzers::ComplexityAnalyzer));
reg.register(Box::new(super::analyzers::DependencyAnalyzer));
reg.register(Box::new(super::analyzers::SecurityAnalyzer));
reg.register(Box::new(super::analyzers::TopologyAnalyzer));
reg.register(Box::new(super::analyzers::DataSourceAnalyzer));
reg.register(Box::new(super::analyzers::NetworkAnalyzer));
reg.register(Box::new(super::analyzers::RabbitMQAnalyzer));
reg.register(Box::new(super::analyzers::TerraformAnalyzer));
reg
}
pub fn register(&mut self, analyzer: Box<dyn Analyzer>) {
self.analyzers.push(analyzer);
}
pub fn run_all(
&self,
project: &Path,
files: &[PathBuf],
context: &AnalysisContext,
) -> Vec<Finding> {
let mut findings = Vec::new();
for analyzer in &self.analyzers {
findings.extend(analyzer.analyze(project, files, context));
}
findings
}
pub fn analyzer_ids(&self) -> Vec<String> {
self.analyzers.iter().map(|a| a.id().to_string()).collect()
}
}
impl Default for AnalyzerRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssessmentDiff {
pub files_added: Vec<String>,
pub files_removed: Vec<String>,
pub findings_new: Vec<Finding>,
pub findings_resolved: Vec<Finding>,
pub complexity_delta: i64,
pub coherence_delta: f64,
}
pub fn diff_reports(current: &AssessmentReport, previous: &AssessmentReport) -> AssessmentDiff {
use std::collections::HashSet;
let current_files: HashSet<String> = current
.findings
.iter()
.map(|f| f.file.clone())
.collect();
let previous_files: HashSet<String> = previous
.findings
.iter()
.map(|f| f.file.clone())
.collect();
let files_added: Vec<String> = current_files.difference(&previous_files).cloned().collect();
let files_removed: Vec<String> = previous_files.difference(¤t_files).cloned().collect();
type FindingKey = (String, String, String);
fn finding_key(f: &Finding) -> FindingKey {
(f.category.clone(), f.file.clone(), f.message.clone())
}
let prev_keys: HashSet<FindingKey> = previous.findings.iter().map(finding_key).collect();
let curr_keys: HashSet<FindingKey> = current.findings.iter().map(finding_key).collect();
let findings_new: Vec<Finding> = current
.findings
.iter()
.filter(|f| !prev_keys.contains(&finding_key(f)))
.cloned()
.collect();
let findings_resolved: Vec<Finding> = previous
.findings
.iter()
.filter(|f| !curr_keys.contains(&finding_key(f)))
.cloned()
.collect();
let complexity_delta = current.summary.complexity_warnings as i64
- previous.summary.complexity_warnings as i64;
let coherence_delta = current.summary.coherence_score - previous.summary.coherence_score;
AssessmentDiff {
files_added,
files_removed,
findings_new,
findings_resolved,
complexity_delta,
coherence_delta,
}
}