main-spex 0.26.0

AI-first development framework that turns specifications into verified code
use crate::spec::{ContractLevel, CoverageLayer, Spec};

use super::super::finding::{Finding, Location, Source};

#[cfg(test)]
#[path = "coverage_ignore_test.rs"]
mod coverage_ignore_test;

pub(super) fn validate_coverage_ignore(spec: &Spec) -> Vec<Finding> {
    let mut findings = validate_contract_waivers(spec);
    findings.extend(validate_waiving_packages_are_listed(spec));
    findings
}

fn validate_contract_waivers(spec: &Spec) -> Vec<Finding> {
    spec.coverage_ignore
        .iter()
        .chain(spec.package_coverage_ignore.values().flatten())
        .filter(|ignore| ignore.layer == CoverageLayer::Contract)
        .filter_map(|ignore| {
            let message = match (spec.contract, ignore.ac.as_ref()) {
                (ContractLevel::None, _) => Some(
                    "`coverage_ignore` waives the contract layer but `contract: none` already disables contract coverage — remove the entry or set `contract` to `single` or `for-each-ac`".to_string(),
                ),
                (ContractLevel::Single, Some(_)) => Some(
                    "`coverage_ignore` waives one acceptance criterion's contract but `contract: single` requires a single spec-level contract — use `contract: for-each-ac` for per-criterion contracts".to_string(),
                ),
                _ => None,
            };
            message.map(|message| coverage_ignore_error(spec, message))
        })
        .collect()
}

fn validate_waiving_packages_are_listed(spec: &Spec) -> Vec<Finding> {
    spec.package_coverage_ignore
        .keys()
        .filter(|short_name| !spec.packages.contains(short_name))
        .map(|short_name| {
            coverage_ignore_error(
                spec,
                format!(
                    "`coverage_ignore` of package `{short_name}` names this spec, but its `packages` does not list `{short_name}` — list the package in `packages` or remove the entry"
                ),
            )
        })
        .collect()
}

fn coverage_ignore_error(spec: &Spec, message: String) -> Finding {
    Finding::error(
        Source::spec(spec.id.as_str(), "coverage-ignore"),
        Some(Location::in_file(&spec.path)),
        message,
    )
}