#![cfg_attr(coverage_nightly, coverage(off))]
use super::types::QualityProfile;
use crate::cli::EnforceOutputFormat;
use anyhow::Result;
use std::path::PathBuf;
pub struct EnforcementConfig {
pub max_iterations: u32,
pub target_improvement: Option<f32>,
pub max_time: Option<u64>,
pub apply_suggestions: bool,
pub specific_file: Option<PathBuf>,
pub include_pattern: Option<String>,
pub exclude_pattern: Option<String>,
pub single_file_mode: bool,
pub dry_run: bool,
pub show_progress: bool,
pub format: EnforceOutputFormat,
pub ci_mode: bool,
}
fn standard_profile() -> QualityProfile {
QualityProfile {
coverage_min: 60.0,
complexity_max: 30,
complexity_target: 15,
tdg_max: 2.5,
satd_allowed: 20,
duplication_max_lines: 200,
big_o_max: "O(n^2)".to_string(),
provability_min: 0.5,
}
}
fn strict_profile() -> QualityProfile {
QualityProfile {
coverage_min: 70.0,
complexity_max: 25,
complexity_target: 12,
tdg_max: 1.5,
satd_allowed: 5,
duplication_max_lines: 50,
big_o_max: "O(n log n)".to_string(),
provability_min: 0.7,
}
}
#[derive(Debug, Default, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct ProfileOverrides {
coverage_min: Option<f64>,
complexity_max: Option<u16>,
complexity_target: Option<u16>,
tdg_max: Option<f64>,
satd_allowed: Option<usize>,
duplication_max_lines: Option<usize>,
big_o_max: Option<String>,
provability_min: Option<f64>,
}
impl ProfileOverrides {
fn apply(self, base: &mut QualityProfile) {
if let Some(v) = self.coverage_min {
base.coverage_min = v;
}
if let Some(v) = self.complexity_max {
base.complexity_max = v;
}
if let Some(v) = self.complexity_target {
base.complexity_target = v;
}
if let Some(v) = self.tdg_max {
base.tdg_max = v;
}
if let Some(v) = self.satd_allowed {
base.satd_allowed = v;
}
if let Some(v) = self.duplication_max_lines {
base.duplication_max_lines = v;
}
if let Some(v) = self.big_o_max {
base.big_o_max = v;
}
if let Some(v) = self.provability_min {
base.provability_min = v;
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn load_quality_profile(
profile_name: &str,
config_path: Option<PathBuf>,
) -> Result<QualityProfile> {
let mut profile = match profile_name {
"standard" => standard_profile(),
"strict" => strict_profile(),
"extreme" => QualityProfile::default(),
other => anyhow::bail!(
"Unknown quality profile: {other}. Valid profiles: standard, strict, extreme"
),
};
if let Some(path) = config_path {
let text = std::fs::read_to_string(&path).map_err(|e| {
anyhow::anyhow!(
"cannot read quality config {}: {e} โ enforce will not report a verdict measured against thresholds it could not load",
path.display()
)
})?;
let overrides: ProfileOverrides = toml::from_str(&text)
.map_err(|e| anyhow::anyhow!("invalid quality config {}: {e}", path.display()))?;
overrides.apply(&mut profile);
}
Ok(profile)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn initialize_enforcement_environment(
profile_name: &str,
config_path: Option<PathBuf>,
cache_dir: &Option<PathBuf>,
clear_cache: bool,
) -> Result<QualityProfile> {
let profile = load_quality_profile(profile_name, config_path)?;
if clear_cache {
clear_enforcement_cache(cache_dir)?;
}
Ok(profile)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn clear_enforcement_cache(cache_dir: &Option<PathBuf>) -> Result<()> {
let Some(cache_path) = cache_dir else {
eprintln!(
"๐งน --clear-cache: no --cache-dir given and enforce keeps no cache of its own โ \
nothing to clear (every phase is recomputed on each run)"
);
return Ok(());
};
crate::cli::cache_clearing::clear_cache_directory_reporting(cache_path, "--clear-cache")?;
Ok(())
}
#[cfg(test)]
mod profile_selection_tests {
use super::load_quality_profile;
#[test]
fn test_named_profiles_have_distinct_thresholds() {
let standard = load_quality_profile("standard", None).unwrap();
let strict = load_quality_profile("strict", None).unwrap();
let extreme = load_quality_profile("extreme", None).unwrap();
assert!(
standard.complexity_max > strict.complexity_max
&& strict.complexity_max > extreme.complexity_max,
"complexity_max must tighten: {} / {} / {}",
standard.complexity_max,
strict.complexity_max,
extreme.complexity_max
);
assert!(
standard.tdg_max > strict.tdg_max && strict.tdg_max > extreme.tdg_max,
"tdg_max must tighten"
);
assert!(
standard.coverage_min < strict.coverage_min
&& strict.coverage_min < extreme.coverage_min,
"coverage_min must rise"
);
assert!(
standard.satd_allowed > strict.satd_allowed
&& strict.satd_allowed > extreme.satd_allowed,
"satd_allowed must tighten"
);
assert!(
standard.duplication_max_lines > strict.duplication_max_lines
&& strict.duplication_max_lines > extreme.duplication_max_lines,
"duplication_max_lines must tighten"
);
}
#[test]
fn test_unknown_profile_is_an_error_not_a_silent_extreme() {
let err = load_quality_profile("toyota", None)
.unwrap_err()
.to_string();
assert!(
err.contains("standard") && err.contains("strict") && err.contains("extreme"),
"the error must name the valid profiles, got {err}"
);
}
}