#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn check_entropy(
project_path: &Path,
min_entropy: f64,
) -> Result<Vec<QualityViolation>> {
check_entropy_with_excludes(project_path, min_entropy, &[]).await
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn check_entropy_with_excludes(
project_path: &Path,
min_entropy: f64,
extra_exclude_paths: &[String],
) -> Result<Vec<QualityViolation>> {
use crate::entropy::EntropyAnalyzer;
let config = build_entropy_config(project_path, min_entropy, extra_exclude_paths);
let analyzer = EntropyAnalyzer::with_config(config);
let report = analyzer.analyze(project_path).await?;
let threshold_context = diversity_context(report.entropy_metrics.pattern_diversity, min_entropy);
Ok(report
.actionable_violations
.into_iter()
.map(|violation| to_quality_violation(violation, &threshold_context))
.collect())
}
fn diversity_context(measured: Option<f64>, min_entropy: f64) -> String {
measured.map_or_else(String::new, |m| {
format!(
" [pattern diversity {:.1}% < required {:.1}% (--min-entropy {})]",
m * 100.0,
min_entropy * 100.0,
min_entropy
)
})
}
fn build_entropy_config(
project_path: &Path,
min_entropy: f64,
extra_exclude_paths: &[String],
) -> crate::entropy::EntropyConfig {
use crate::entropy::violation_detector::Severity;
use crate::entropy::EntropyConfig;
let mut config = EntropyConfig {
min_severity: Severity::Medium, min_pattern_diversity: min_entropy,
max_pattern_repetition: load_max_pattern_repetition(project_path),
exclude_paths: EntropyConfig::analysis_excludes(false),
..Default::default()
};
for path in extra_exclude_paths {
let pattern = if path.contains('*') {
path.clone()
} else {
format!("{}**", path.trim_end_matches('/').to_owned() + "/")
};
config.exclude_paths.push(pattern);
}
config.with_project_ignores(project_path)
}
fn to_quality_violation(
violation: crate::entropy::ActionableViolation,
diversity_context: &str,
) -> QualityViolation {
use crate::entropy::violation_detector::Severity;
let context = if violation.pattern.is_none() {
diversity_context
} else {
""
};
QualityViolation {
check_type: "entropy".to_string(),
severity: match violation.severity {
Severity::Low => "info".to_string(),
Severity::Medium => "warning".to_string(),
Severity::High => "error".to_string(),
},
file: violation.affected_files.first().map_or_else(
|| "project".to_string(),
|p| p.to_string_lossy().to_string(),
),
line: None, message: format!(
"{} (saves {}) - Fix: {}{}",
violation.message,
violation.render_loc_reduction(),
violation.fix_suggestion,
context
),
details: Some(ViolationDetails {
affected_files: violation
.affected_files
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect(),
example_code: violation.pattern.as_ref().map(|p| p.example_code.clone()),
fix_suggestion: Some(violation.fix_suggestion.clone()),
score_factors: violation.pattern.as_ref().map_or_else(
|| vec!["scope: project-wide pattern distribution".to_string()],
|p| {
vec![
format!("pattern_type: {:?}", p.pattern_type),
format!("repetitions: {}", p.repetitions),
format!("variation_score: {:.2}", p.variation_score),
]
},
),
}),
}
}
fn load_max_pattern_repetition(project_path: &Path) -> usize {
for filename in &[".pmat-gates.toml", ".pmat-metrics.toml"] {
let path = project_path.join(filename);
if let Ok(content) = std::fs::read_to_string(&path) {
if let Ok(table) = content.parse::<toml::Table>() {
if let Some(val) = table
.get("entropy")
.and_then(|t| t.get("max_pattern_repetition"))
.and_then(|v| v.as_integer())
{
return val.max(1) as usize;
}
}
}
}
if let Ok(content) = std::fs::read_to_string(project_path.join("pmat.toml")) {
if let Ok(table) = content.parse::<toml::Table>() {
if let Some(val) = table
.get("quality")
.and_then(|t| t.get("max_pattern_repetition"))
.and_then(|v| v.as_integer())
{
return val.max(1) as usize;
}
}
}
5 }