impl TdgAnalyzer {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Result<Self> {
Ok(Self {
config: TdgConfig::default(),
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn with_config(config: TdgConfig) -> Result<Self> {
Ok(Self { config })
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn analyze_file(&self, path: &Path) -> Result<TdgScore> {
let language = Language::from_extension(path);
let source = fs::read_to_string(path)?;
self.analyze_source(&source, language, Some(path.to_path_buf()))
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn analyze_source(
&self,
source: &str,
language: Language,
file_path: Option<PathBuf>,
) -> Result<TdgScore> {
let mut tracker = PenaltyTracker::new();
let mut score = TdgScore {
language,
confidence: language.confidence(),
file_path,
..Default::default()
};
score.structural_complexity = self.analyze_structural_complexity(source, &mut tracker);
score.semantic_complexity = self.analyze_semantic_complexity(source, &mut tracker);
score.duplication_ratio = self.analyze_duplication(source, &mut tracker);
score.coupling_score = self.analyze_coupling(source, &mut tracker);
score.doc_coverage = self.analyze_documentation(source, language, &mut tracker);
score.consistency_score = self.analyze_consistency(source, language, &mut tracker);
if language == Language::Lean {
let sorry_count = count_lean_sorry(source);
if sorry_count > 0 {
score.has_critical_defects = true;
score.critical_defects_count = sorry_count;
}
}
score.penalties_applied = tracker.get_attributions();
score.calculate_total();
Ok(score)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn analyze_project(&self, dir: &Path) -> Result<ProjectScore> {
let files = self.discover_files(dir)?;
let mut scores = Vec::new();
let contracted_paths = collect_contracted_file_paths(dir);
for file in &files {
if crate::cli::language_analyzer::is_include_fragment(file) {
continue;
}
match self.analyze_file(file) {
Ok(mut score) => {
apply_contract_coverage(&mut score, dir, &contracted_paths);
scores.push(score);
}
Err(e) => {
if !crate::cli::language_analyzer::is_include_fragment(file) {
eprintln!("Warning: Failed to analyze {}: {}", file.display(), e);
}
}
}
}
Ok(ProjectScore::aggregate(scores))
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn compare(&self, path1: &Path, path2: &Path) -> Result<Comparison> {
let score1 = if path1.is_dir() {
self.analyze_project(path1)?.average()
} else {
self.analyze_file(path1)?
};
let score2 = if path2.is_dir() {
self.analyze_project(path2)?.average()
} else {
self.analyze_file(path2)?
};
Ok(Comparison::new(score1, score2))
}
}
fn apply_contract_coverage(score: &mut TdgScore, dir: &Path, contracted_paths: &[String]) {
if contracted_paths.is_empty() {
return;
}
let fp = match score.file_path {
Some(ref fp) => fp,
None => return,
};
let rel = fp.strip_prefix(dir).unwrap_or(fp);
let rel_str = rel.to_string_lossy();
score.has_contract_coverage = contracted_paths.iter().any(|cp| rel_str.contains(cp));
if score.has_contract_coverage {
score.calculate_total();
}
}
fn collect_contracted_file_paths(project_path: &Path) -> Vec<String> {
let binding_yaml = match resolve_binding_yaml(project_path) {
Some(p) => p,
None => return Vec::new(),
};
let content = match fs::read_to_string(&binding_yaml) {
Ok(c) => c,
Err(_) => return Vec::new(),
};
let mut paths = Vec::new();
let mut in_implemented = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("status:") {
in_implemented = trimmed.contains("implemented");
continue;
}
if trimmed.starts_with("module_path:") && in_implemented {
if let Some(val) = trimmed.strip_prefix("module_path:") {
let module_path = val.trim().trim_matches('"').trim_matches('\'');
let parts: Vec<&str> = module_path.split("::").collect();
if parts.len() >= 2 {
let file_frag = parts[1..].join("/");
if !file_frag.is_empty() {
paths.push(file_frag);
}
}
}
}
if trimmed.starts_with("- contract:") {
in_implemented = false;
}
}
paths
}
fn resolve_binding_yaml(project_path: &Path) -> Option<PathBuf> {
let local = project_path.join("contracts").join("binding.yaml");
if local.exists() {
return Some(local);
}
let abs = std::fs::canonicalize(project_path).ok()?;
let parent = abs.parent()?;
let pv_contracts = parent.join("provable-contracts").join("contracts");
if !pv_contracts.exists() {
return None;
}
let dir_name = abs.file_name()?.to_str()?;
let by_dir = pv_contracts.join(dir_name).join("binding.yaml");
if by_dir.exists() {
return Some(by_dir);
}
let cargo_toml = project_path.join("Cargo.toml");
if let Ok(content) = fs::read_to_string(&cargo_toml) {
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("name") && trimmed.contains('=') {
if let Some(name) = trimmed.split('=').nth(1) {
let pkg = name.trim().trim_matches('"');
let by_pkg = pv_contracts.join(pkg).join("binding.yaml");
if by_pkg.exists() {
return Some(by_pkg);
}
}
break;
}
}
}
None
}