xbp-analysis 10.57.0

Language-agnostic static analysis (error-handling / Aspirator-style) for XBP
Documentation
//! Safe autofixes only — never rewrites recovery/retry/transaction logic.

use crate::domain::types::Finding;
use crate::domain::types::SourceFile;
use crate::ports::inbound::{AppliedFix, FixService};
use crate::ports::outbound::SourceWriter;
use std::fs;
use std::path::Path;

/// Writes source files to disk.
pub struct FsSourceWriter;

impl SourceWriter for FsSourceWriter {
    fn write(&self, path: &Path, content: &str) -> Result<(), String> {
        fs::write(path, content).map_err(|e| e.to_string())
    }
}

/// Only applies fixes marked as unambiguous and safe by rule metadata / fix_hint patterns.
pub struct SafeFixApplier {
    writer: Box<dyn SourceWriter>,
}

impl SafeFixApplier {
    pub fn new(writer: Box<dyn SourceWriter>) -> Self {
        Self { writer }
    }

    pub fn default_fs() -> Self {
        Self::new(Box::new(FsSourceWriter))
    }
}

impl FixService for SafeFixApplier {
    fn apply_safe_fixes(
        &self,
        findings: &[Finding],
        sources: &[(SourceFile, String)],
    ) -> Result<Vec<AppliedFix>, String> {
        let applied = Vec::new();
        // Policy: refuse distributed / retry / transaction related rules always.
        const FORBIDDEN: &[&str] = &[
            "unbounded-retry",
            "non-idempotent-retry",
            "partial-commit",
            "error-context-loss",
            "unobserved-task-failure",
            "missing-cleanup",
        ];

        for f in findings {
            if FORBIDDEN.contains(&f.rule_id.as_str()) {
                continue;
            }
            // Currently only document intent; real rewrites require explicit fix_hint
            // patterns that are purely mechanical (none enabled by default).
            let _ = (f, sources);
        }
        // No automatic rewrites of error propagation without explicit approval.
        let _ = &self.writer;
        Ok(applied)
    }
}