impl AccurateComplexityAnalyzer {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_file(&self, path: &Path) -> Result<FileComplexityResult> {
let content = tokio::fs::read_to_string(path).await?;
let ast = syn::parse_file(&content)?;
let mut line_map = build_function_line_map(&content);
let total_lines = line_map.total_lines();
let mut functions = Vec::new();
for item in ast.items {
if let Item::Fn(func) = item {
let name = func.sig.ident.to_string();
let span = line_map.take(&name).unwrap_or(LineSpan::UNKNOWN);
let metrics = self.analyze_function(&func, span);
functions.push(metrics);
}
}
Ok(FileComplexityResult {
functions,
file_path: path.display().to_string(),
total_lines,
})
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub async fn analyze_project(&self, path: &Path) -> Result<ProjectComplexityResult> {
let mut file_metrics = Vec::new();
let mut files_analyzed = 0;
for entry in WalkDir::new(path)
.into_iter()
.filter_map(std::result::Result::ok)
.filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
{
let file_path = entry.path();
if self.exclude_tests && self.is_test_file(file_path) {
continue;
}
if let Ok(result) = self.analyze_file(file_path).await {
files_analyzed += 1;
file_metrics.push(result);
}
}
Ok(ProjectComplexityResult {
files_analyzed,
file_metrics,
})
}
fn analyze_function(&self, func: &ItemFn, span: LineSpan) -> FunctionMetrics {
let name = func.sig.ident.to_string();
let suppressed = self.respect_annotations && self.has_suppress_annotation(&func.attrs);
let complexity = measure_block(&name, &func.block);
FunctionMetrics {
name,
cyclomatic_complexity: complexity.cyclomatic,
cognitive_complexity: complexity.cognitive,
max_nesting: complexity.max_nesting,
suppressed,
line_start: span.start,
line_end: span.end,
}
}
fn is_test_file(&self, path: &Path) -> bool {
let path_str = path.to_string_lossy();
path_str.contains("/tests/")
|| path_str.contains("/test/")
|| path_str.ends_with("_test.rs")
|| path_str.ends_with("_tests.rs")
|| path_str.contains("test_")
|| path_str.contains("tests.rs")
}
fn has_suppress_annotation(&self, attrs: &[Attribute]) -> bool {
attrs.iter().any(|attr| {
if attr.path().is_ident("allow") {
let tokens_str = attr
.meta
.require_list()
.map(|list| list.tokens.to_string())
.unwrap_or_default();
tokens_str.contains("complex_function")
} else {
false
}
})
}
}