use anyhow::Result;
use crabscore_core::metrics::SafetyMetrics;
use syn::{visit::Visit, ItemFn};
use walkdir::WalkDir;
struct UnsafeCounter {
count: u32,
}
impl<'ast> Visit<'ast> for UnsafeCounter {
fn visit_expr_unsafe(&mut self, _i: &'ast syn::ExprUnsafe) {
self.count += 1;
}
}
struct ComplexityVisitor {
branches: u32,
}
impl<'ast> Visit<'ast> for ComplexityVisitor {
fn visit_expr_if(&mut self, _: &'ast syn::ExprIf) {
self.branches += 1;
}
fn visit_expr_match(&mut self, _: &'ast syn::ExprMatch) {
self.branches += 1;
}
fn visit_expr_for_loop(&mut self, _: &'ast syn::ExprForLoop) {
self.branches += 1;
}
fn visit_expr_while(&mut self, _: &'ast syn::ExprWhile) {
self.branches += 1;
}
}
pub fn analyse_project<P: AsRef<std::path::Path>>(root: P) -> Result<SafetyMetrics> {
let mut unsafe_blocks = 0u32;
let mut total_complexity = 0u32;
let mut fn_count = 0u32;
for entry in WalkDir::new(root) {
let entry = entry?;
if entry.file_type().is_file()
&& entry.path().extension().and_then(|s| s.to_str()) == Some("rs")
{
let src = std::fs::read_to_string(entry.path())?;
let syntax = syn::parse_file(&src)?;
let mut counter = UnsafeCounter { count: 0 };
counter.visit_file(&syntax);
unsafe_blocks += counter.count;
for item in syntax.items.iter() {
if let syn::Item::Fn(ItemFn { block, .. }) = item {
let mut visitor = ComplexityVisitor { branches: 0 };
visitor.visit_block(block);
total_complexity += visitor.branches + 1; fn_count += 1;
}
}
}
}
let avg_cyclo = if fn_count > 0 {
total_complexity as f64 / fn_count as f64
} else {
1.0
};
Ok(SafetyMetrics {
unsafe_blocks,
clippy_warnings: 0, avg_cyclomatic: avg_cyclo,
})
}