use super::types::{CbPatternViolation, Severity};
use std::path::Path;
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn detect_cb533_stale_path_references(project_path: &Path) -> Vec<CbPatternViolation> {
let mut violations = Vec::new();
check_makefile(project_path, &mut violations);
check_ci_workflows(project_path, &mut violations);
violations
}
fn check_makefile(project_path: &Path, violations: &mut Vec<CbPatternViolation>) {
let makefile = project_path.join("Makefile");
if !makefile.exists() {
return;
}
let content = match std::fs::read_to_string(&makefile) {
Ok(c) => c,
Err(_) => return,
};
for (i, line) in content.lines().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with('#') || trimmed.is_empty() {
continue;
}
if let Some(pos) = trimmed.find("cd ") {
let rest = &trimmed[pos + 3..];
let dir = rest.split([';', '&', ' ', '/']).next().unwrap_or("");
if !dir.is_empty()
&& !dir.starts_with('$')
&& !dir.starts_with('-')
&& dir != "."
&& dir != ".."
{
let target = project_path.join(dir);
if !target.exists() {
violations.push(CbPatternViolation {
pattern_id: "CB-533".to_string(),
file: "Makefile".to_string(),
line: i + 1,
description: format!(
"Stale path: `cd {dir}` references non-existent directory"
),
severity: Severity::Warning,
});
}
}
}
}
}
fn check_ci_workflows(project_path: &Path, violations: &mut Vec<CbPatternViolation>) {
let workflows_dir = project_path.join(".github/workflows");
if !workflows_dir.exists() {
return;
}
let entries = match std::fs::read_dir(&workflows_dir) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
if path.extension().map_or(true, |e| e != "yml" && e != "yaml") {
continue;
}
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let filename = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
for (i, line) in content.lines().enumerate() {
let trimmed = line.trim();
if let Some(pos) = trimmed.find("working-directory:") {
let dir = trimmed[pos + 18..]
.trim()
.trim_matches(|c| c == '\'' || c == '"');
if !dir.starts_with('$') && !dir.starts_with('.') {
let target = project_path.join(dir);
if !target.exists() {
violations.push(CbPatternViolation {
pattern_id: "CB-533".to_string(),
file: format!(".github/workflows/{filename}"),
line: i + 1,
description: format!(
"Stale path: `working-directory: {dir}` references non-existent directory"
),
severity: Severity::Warning,
});
}
}
}
}
}
}