xbp-analysis 10.57.0

Language-agnostic static analysis (error-handling / Aspirator-style) for XBP
Documentation
use crate::domain::report::AnalysisReport;
use crate::ports::outbound::DiagnosticRenderer;

pub struct TerminalRenderer;

impl DiagnosticRenderer for TerminalRenderer {
    fn render(&self, report: &AnalysisReport) -> Result<String, String> {
        let mut out = String::new();
        out.push_str(&format!(
            "xbp audit errors — {} finding(s) in {} file(s) ({} ms)\n",
            report.findings.len(),
            report.files_analyzed,
            report.duration_ms
        ));
        if report.findings.is_empty() {
            out.push_str("No issues found.\n");
            return Ok(out);
        }
        for f in &report.findings {
            let kind = if f.definite_bug {
                "bug"
            } else {
                "review"
            };
            out.push_str(&format!(
                "\n[{}] {} {} ({} / {})\n  --> {}:{}:{}\n  {}\n  scenario: {}\n  fix: {}\n",
                f.severity,
                f.rule_id,
                kind,
                f.confidence.as_str(),
                f.language,
                f.location.file.display(),
                f.location.start_line,
                f.location.start_column,
                f.explanation,
                f.failure_scenario,
                f.remediation,
            ));
            for e in &f.evidence {
                out.push_str(&format!("  evidence[{}]: {}\n", e.kind, e.detail));
            }
        }
        out.push_str(&format!(
            "\nSummary: {} definite, {} review-required\n",
            report.stats.definite_bugs, report.stats.review_required
        ));
        Ok(out)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::types::{
        Confidence, Finding, LanguageId, Severity, SourceLocation,
    };

    #[test]
    fn empty_report() {
        let r = AnalysisReport::default();
        let s = TerminalRenderer.render(&r).unwrap();
        assert!(s.contains("No issues found"));
    }

    #[test]
    fn with_finding() {
        let mut r = AnalysisReport::default();
        r.findings.push(
            Finding::builder("ignored-failure", LanguageId::rust())
                .severity(Severity::High)
                .confidence(Confidence::High)
                .location(SourceLocation::single_line("a.rs", 1, 1))
                .explanation("ignored")
                .failure_scenario("silent")
                .remediation("handle")
                .build(),
        );
        r.recompute_stats();
        let s = TerminalRenderer.render(&r).unwrap();
        assert!(s.contains("ignored-failure"));
        assert!(s.contains("a.rs:1:1"));
    }
}