impl CoverageImprovementService {
async fn prioritize_targets(&self) -> Result<Vec<PathBuf>> {
crate::status_eprintln!("🎯 Prioritizing files for test generation...");
let complexity_fut = self.run_pmat_analyze("complexity");
let satd_fut = self.run_pmat_analyze("satd");
let dead_code_fut = self.run_pmat_analyze("dead-code");
let churn_fut = self.run_pmat_analyze("churn");
let (complexity_output, satd_output, dead_code_output, churn_output) =
tokio::try_join!(complexity_fut, satd_fut, dead_code_fut, churn_fut)?;
let mut file_scores: std::collections::HashMap<PathBuf, f64> =
std::collections::HashMap::new();
self.parse_and_score(&complexity_output, &mut file_scores, 0.4)?;
self.parse_and_score(&satd_output, &mut file_scores, 0.3)?;
self.parse_and_score(&dead_code_output, &mut file_scores, 0.2)?;
self.parse_and_score(&churn_output, &mut file_scores, 0.1)?;
file_scores.retain(|path, _score| {
let path_str = path.to_string_lossy();
if !self.config.exclude_patterns.is_empty() {
for pattern in &self.config.exclude_patterns {
if glob::Pattern::new(pattern)
.ok()
.map(|p| p.matches(&path_str))
.unwrap_or(false)
{
return false;
}
}
}
if !self.config.focus_patterns.is_empty() {
for pattern in &self.config.focus_patterns {
if glob::Pattern::new(pattern)
.ok()
.map(|p| p.matches(&path_str))
.unwrap_or(false)
{
return true;
}
}
return false;
}
true
});
let mut files_vec: Vec<(PathBuf, f64)> = file_scores.into_iter().collect();
files_vec.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let top_n = 10; let targets: Vec<PathBuf> = files_vec
.into_iter()
.take(top_n)
.map(|(path, score)| {
crate::status_eprintln!(" 📄 {} (score: {:.2})", path.display(), score);
path
})
.collect();
crate::status_eprintln!("✅ Prioritized {} files", targets.len());
Ok(targets)
}
async fn run_pmat_analyze(&self, analysis_type: &str) -> Result<String> {
let output = Command::new("pmat")
.args(["analyze", analysis_type, "--format", "json"])
.current_dir(&self.config.project_path)
.output()
.await
.context(format!(
"Failed to execute `pmat analyze {}`",
analysis_type
))?;
if !output.status.success() {
eprintln!(
"⚠️ `pmat analyze {}` returned non-zero exit code, using empty results",
analysis_type
);
return Ok("{}".to_string());
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn parse_and_score(
&self,
output: &str,
file_scores: &mut std::collections::HashMap<PathBuf, f64>,
weight: f64,
) -> Result<()> {
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(output) {
self.extract_files_from_json(&json_value, file_scores, weight);
} else {
for line in output.lines() {
if let Some(path) = self.extract_file_path_from_line(line) {
*file_scores.entry(path).or_insert(0.0) += weight;
}
}
}
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn extract_files_from_json(
&self,
json: &serde_json::Value,
file_scores: &mut std::collections::HashMap<PathBuf, f64>,
weight: f64,
) {
match json {
serde_json::Value::Object(map) => {
if let Some(file_path) = map
.get("file")
.or_else(|| map.get("path"))
.or_else(|| map.get("file_path"))
{
if let Some(path_str) = file_path.as_str() {
let path = PathBuf::from(path_str);
*file_scores.entry(path).or_insert(0.0) += weight;
}
}
for value in map.values() {
self.extract_files_from_json(value, file_scores, weight);
}
}
serde_json::Value::Array(arr) => {
for value in arr {
self.extract_files_from_json(value, file_scores, weight);
}
}
_ => {}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub(crate) fn extract_file_path_from_line(&self, line: &str) -> Option<PathBuf> {
let parts: Vec<&str> = line.split_whitespace().collect();
for part in parts {
if part.contains(".rs") || part.contains(".toml") || part.contains(".md") {
return Some(PathBuf::from(part));
}
}
None
}
}