#![cfg_attr(coverage_nightly, coverage(off))]
use super::assessment::assess_project;
use super::types::{
EnforcementProgress, EnforcementResult, EnforcementState, QualityProfile, QualityViolation,
};
use crate::cli::colors as c;
use anyhow::Result;
use std::path::{Path, PathBuf};
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_analyzing_state(
project_path: &Path,
profile: &QualityProfile,
single_file_mode: bool,
_dry_run: bool,
specific_file: Option<&PathBuf>,
include_pattern: Option<&String>,
exclude_pattern: Option<&String>,
) -> Result<EnforcementResult> {
let assessment = assess_project(
project_path,
profile,
specific_file.map(PathBuf::as_path),
include_pattern,
exclude_pattern,
)
.await?;
let next_state = assessment.verdict_state();
let files_remaining = if single_file_mode {
usize::from(assessment.violations.iter().any(is_finding))
} else {
distinct_violation_files(&assessment.violations)
};
let files_completed = assessment.files_examined.saturating_sub(files_remaining);
Ok(EnforcementResult {
state: next_state,
score: assessment.score,
target: 1.0,
current_file: specific_file.map(|p| p.display().to_string()),
violations: assessment.violations,
next_action: if next_state == EnforcementState::Complete {
"none".to_string()
} else {
"review_violations".to_string()
},
progress: EnforcementProgress {
files_completed,
files_remaining,
estimated_iterations: ((1.0 - assessment.score) * 10.0) as u32,
},
})
}
fn is_finding(violation: &QualityViolation) -> bool {
violation.violation_type != "not_measured"
}
fn distinct_violation_files(violations: &[QualityViolation]) -> usize {
violations
.iter()
.filter(|v| is_finding(v))
.map(|v| v.location.split(':').next().unwrap_or(&v.location))
.collect::<std::collections::BTreeSet<_>>()
.len()
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn handle_violating_state(
violations: Vec<QualityViolation>,
total_score: f64,
apply_suggestions: bool,
dry_run: bool,
specific_file: Option<&PathBuf>,
) -> Result<EnforcementResult> {
if apply_suggestions && !dry_run {
Ok(EnforcementResult {
state: EnforcementState::Refactoring,
score: total_score,
target: 1.0,
current_file: specific_file.map(|p| p.display().to_string()),
violations: violations.clone(),
next_action: "apply_refactoring".to_string(),
progress: EnforcementProgress {
files_completed: 0,
files_remaining: violations.len(),
estimated_iterations: violations.len() as u32,
},
})
} else {
Ok(EnforcementResult {
state: EnforcementState::Violating,
score: total_score,
target: 1.0,
current_file: specific_file.map(|p| p.display().to_string()),
violations,
next_action: "manual_intervention_required".to_string(),
progress: EnforcementProgress {
files_completed: 0,
files_remaining: 0,
estimated_iterations: 0,
},
})
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_refactoring_pass(
project_path: &Path,
profile: &QualityProfile,
single_file_mode: bool,
dry_run: bool,
specific_file: Option<&PathBuf>,
include_pattern: Option<&String>,
exclude_pattern: Option<&String>,
) -> Result<EnforcementResult> {
eprintln!(
"{}",
c::warn(
"--apply-suggestions: no automated refactoring is implemented, so nothing was changed"
)
);
let mut measured = handle_analyzing_state(
project_path,
profile,
single_file_mode,
dry_run,
specific_file,
include_pattern,
exclude_pattern,
)
.await?;
if measured.state != EnforcementState::Complete {
measured.state = EnforcementState::Validating;
measured.next_action = "validate_changes".to_string();
}
Ok(measured)
}
#[cfg(test)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn handle_refactoring_state(
total_score: f64,
specific_file: Option<&PathBuf>,
) -> Result<EnforcementResult> {
eprintln!("🔧 Applying automated refactoring...");
Ok(EnforcementResult {
state: EnforcementState::Validating,
score: total_score + 0.1, target: 1.0,
current_file: specific_file.map(|p| p.display().to_string()),
violations: vec![], next_action: "validate_changes".to_string(),
progress: EnforcementProgress {
files_completed: 1,
files_remaining: 0,
estimated_iterations: 1,
},
})
}
#[cfg(test)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn handle_complete_state() -> Result<EnforcementResult> {
Ok(EnforcementResult {
state: EnforcementState::Complete,
score: 1.0,
target: 1.0,
current_file: None,
violations: vec![],
next_action: "none".to_string(),
progress: EnforcementProgress {
files_completed: 100, files_remaining: 0,
estimated_iterations: 0,
},
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_violating_enforcement_state_proxy(
project_path: &PathBuf,
profile: &QualityProfile,
single_file_mode: bool,
dry_run: bool,
specific_file: Option<&PathBuf>,
apply_suggestions: bool,
include_pattern: Option<&String>,
exclude_pattern: Option<&String>,
) -> Result<EnforcementResult> {
let analyzing_result = handle_analyzing_state(
project_path,
profile,
single_file_mode,
dry_run,
specific_file,
include_pattern,
exclude_pattern,
)
.await?;
let measured = analyzing_result.progress.clone();
let mut result = handle_violating_state(
analyzing_result.violations,
analyzing_result.score,
apply_suggestions,
dry_run,
specific_file,
)?;
result.progress = measured;
Ok(result)
}
#[cfg(test)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn handle_refactoring_enforcement_state(
base_score: f64,
specific_file: Option<&PathBuf>,
) -> Result<EnforcementResult> {
handle_refactoring_state(base_score, specific_file)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_validating_enforcement_state(
project_path: &PathBuf,
profile: &QualityProfile,
single_file_mode: bool,
dry_run: bool,
specific_file: Option<&PathBuf>,
include_pattern: Option<&String>,
exclude_pattern: Option<&String>,
) -> Result<EnforcementResult> {
handle_analyzing_state(
project_path,
profile,
single_file_mode,
dry_run,
specific_file,
include_pattern,
exclude_pattern,
)
.await
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn handle_analyzing_enforcement_state(
project_path: &PathBuf,
profile: &QualityProfile,
single_file_mode: bool,
dry_run: bool,
specific_file: Option<&PathBuf>,
) -> Result<EnforcementResult> {
handle_analyzing_state(
project_path,
profile,
single_file_mode,
dry_run,
specific_file,
None,
None,
)
.await
}
#[cfg(test)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn handle_complete_enforcement_state() -> Result<EnforcementResult> {
handle_complete_state()
}
#[cfg(test)]
mod composite_score_regression_tests {
use super::super::assessment::phase_score;
use super::*;
fn strict_profile() -> QualityProfile {
QualityProfile {
complexity_max: 1,
complexity_target: 1,
tdg_max: 1000.0,
..QualityProfile::default()
}
}
fn write_project(dir: &Path, source: &str) {
std::fs::write(
dir.join("Cargo.toml"),
"[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.expect("write Cargo.toml");
std::fs::create_dir_all(dir.join("src")).expect("create src");
std::fs::write(dir.join("src").join("lib.rs"), source).expect("write lib.rs");
std::fs::write(
dir.join("lcov.info"),
"SF:src/lib.rs\nDA:1,1\nLF:1\nLH:1\nend_of_record\n",
)
.expect("write lcov.info");
}
#[tokio::test]
async fn score_distinguishes_an_empty_directory_from_a_violating_project() {
let empty = tempfile::tempdir().expect("tempdir");
let profile = strict_profile();
let empty_result =
handle_analyzing_state(empty.path(), &profile, false, true, None, None, None)
.await
.expect("analyze empty directory");
assert!(
empty_result
.violations
.iter()
.all(|v| v.violation_type == "not_measured"),
"an empty directory violates nothing: {:?}",
empty_result.violations
);
assert!(
empty_result
.violations
.iter()
.any(|v| v.violation_type == "not_measured"),
"the unmeasured dimensions must be visible: {:?}",
empty_result.violations
);
assert!(
empty_result.score < 1.0,
"an assessment covering one dimension of three cannot score full marks: got {}",
empty_result.score
);
assert_ne!(empty_result.state, EnforcementState::Complete);
let violating = tempfile::tempdir().expect("tempdir");
write_project(
violating.path(),
"pub fn branchy(a: i32, b: i32) -> i32 {\n\
\x20 if a > 0 {\n\
\x20 if b > 0 {\n\
\x20 return 1;\n\
\x20 }\n\
\x20 return 2;\n\
\x20 }\n\
\x20 match a {\n\
\x20 1 => 3,\n\
\x20 2 => 4,\n\
\x20 3 => 5,\n\
\x20 _ => 6,\n\
\x20 }\n\
}\n",
);
let violating_result =
handle_analyzing_state(violating.path(), &profile, false, true, None, None, None)
.await
.expect("analyze violating project");
assert!(
violating_result
.violations
.iter()
.any(|v| v.violation_type == "complexity"),
"fixture must breach complexity_max = 1: {:?}",
violating_result.violations
);
assert!(
(violating_result.score - empty_result.score).abs() > f64::EPSILON,
"a violating project ({}) must not score the same as an empty directory ({})",
violating_result.score,
empty_result.score
);
assert!(
violating_result
.violations
.iter()
.all(|v| v.violation_type != "not_measured"),
"a parseable project measures every dimension: {:?}",
violating_result.violations
);
}
#[tokio::test]
async fn files_completed_counts_the_clean_files_the_run_actually_read() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"fixture\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
)
.expect("write Cargo.toml");
std::fs::create_dir_all(dir.path().join("src")).expect("create src");
std::fs::write(
dir.path().join("src/lib.rs"),
"pub mod clean;\npub mod branchy;\n",
)
.expect("write lib.rs");
std::fs::write(
dir.path().join("src/clean.rs"),
"pub fn double(a: i32) -> i32 {\n a * 2\n}\n",
)
.expect("write clean.rs");
std::fs::write(
dir.path().join("src/branchy.rs"),
"pub fn branchy(a: i32, b: i32) -> i32 {\n\
\x20 if a > 0 {\n\
\x20 if b > 0 {\n\
\x20 return 1;\n\
\x20 }\n\
\x20 return 2;\n\
\x20 }\n\
\x20 match a {\n\
\x20 1 => 3,\n\
\x20 2 => 4,\n\
\x20 3 => 5,\n\
\x20 _ => 6,\n\
\x20 }\n\
}\n",
)
.expect("write branchy.rs");
let profile = QualityProfile {
complexity_max: 2,
complexity_target: 2,
tdg_max: 1000.0,
..QualityProfile::default()
};
let result = handle_analyzing_state(dir.path(), &profile, false, true, None, None, None)
.await
.expect("analyze fixture");
assert_eq!(
result.progress.files_remaining, 1,
"exactly one file breaches the profile: {:?}",
result.violations
);
assert_eq!(
result.progress.files_completed, 2,
"three source files were read and one carries a finding, so two are \
done — this was the literal 0: {:?}",
result.progress
);
assert!(
result
.violations
.iter()
.any(|v| v.violation_type == "not_measured"),
"the fixture carries no lcov report, so coverage is disclosed: {:?}",
result.violations
);
let empty = tempfile::tempdir().expect("tempdir");
let empty_result =
handle_analyzing_state(empty.path(), &profile, false, true, None, None, None)
.await
.expect("analyze empty directory");
assert_eq!(
empty_result.progress.files_completed, 0,
"a directory with no source file completes none of them: {:?}",
empty_result.progress
);
assert_eq!(
empty_result.progress.files_remaining, 0,
"and no file carries a finding there either: {:?}",
empty_result.progress
);
}
#[tokio::test]
async fn every_state_reports_the_progress_that_was_measured() {
let dir = tempfile::tempdir().expect("tempdir");
write_project(
dir.path(),
"pub fn branchy(a: i32, b: i32) -> i32 {\n\
\x20 if a > 0 {\n\
\x20 if b > 0 {\n\
\x20 return 1;\n\
\x20 }\n\
\x20 return 2;\n\
\x20 }\n\
\x20 a\n\
}\n",
);
let profile = QualityProfile {
complexity_max: 1,
complexity_target: 1,
tdg_max: 1000.0,
..QualityProfile::default()
};
let analyzing = handle_analyzing_state(dir.path(), &profile, false, true, None, None, None)
.await
.expect("analyze");
assert_eq!(analyzing.progress.files_remaining, 1);
let violating = handle_violating_enforcement_state_proxy(
&dir.path().to_path_buf(),
&profile,
false,
true,
None,
false,
None,
None,
)
.await
.expect("violating step");
assert_eq!(
violating.progress.files_remaining, analyzing.progress.files_remaining,
"the violating step describes the same run: {:?} vs {:?}",
violating.progress, analyzing.progress
);
assert_eq!(
violating.progress.files_completed, analyzing.progress.files_completed,
"the violating step describes the same run: {:?} vs {:?}",
violating.progress, analyzing.progress
);
}
#[test]
fn phase_score_is_clean_when_nothing_was_reported() {
assert!((phase_score(&[]) - 1.0).abs() < f64::EPSILON);
}
#[test]
fn phase_score_scales_with_the_worst_overshoot() {
let violations = vec![
QualityViolation {
violation_type: "complexity".to_string(),
severity: "medium".to_string(),
location: "a.rs:1:f".to_string(),
current: 20.0,
target: 10.0,
suggestion: String::new(),
},
QualityViolation {
violation_type: "complexity".to_string(),
severity: "high".to_string(),
location: "b.rs:1:g".to_string(),
current: 40.0,
target: 10.0,
suggestion: String::new(),
},
];
assert!((phase_score(&violations) - 0.25).abs() < 1e-9);
assert_eq!(distinct_violation_files(&violations), 2);
}
}