use crate::language::Language;
use super::stats::Complexity;
pub struct ComplexityAnalyzer;
impl ComplexityAnalyzer {
pub fn new() -> Self {
Self
}
pub fn analyze(&self, content: &str, lang: &Language) -> Complexity {
let mut complexity = Complexity::default();
let patterns = lang.complexity_patterns();
if let Some(ref re) = patterns.function_re {
complexity.functions = re.find_iter(content).count();
}
if let Some(ref re) = patterns.keywords_re {
complexity.cyclomatic = re.find_iter(content).count();
}
complexity.cyclomatic += complexity.functions;
complexity.max_depth = self.calculate_max_depth(content);
if complexity.functions > 0 {
let total_lines = content.lines().count();
complexity.avg_func_lines = total_lines as f64 / complexity.functions as f64;
}
complexity
}
fn calculate_max_depth(&self, content: &str) -> usize {
let mut max_depth: usize = 0;
let mut current_depth: usize = 0;
for c in content.chars() {
match c {
'{' | '(' | '[' => {
current_depth += 1;
max_depth = max_depth.max(current_depth);
}
'}' | ')' | ']' => {
current_depth = current_depth.saturating_sub(1);
}
_ => {}
}
}
max_depth
}
}
impl Default for ComplexityAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_rust_lang() -> Language {
Language {
name: "Rust".to_string(),
extensions: vec![".rs".to_string()],
line_comments: vec!["//".to_string()],
block_comments: vec![("/*".to_string(), "*/".to_string())],
function_pattern: Some(r"(?m)^\s*(pub\s+)?(async\s+)?fn\s+\w+".to_string()),
complexity_keywords: vec![
"if".to_string(),
"else".to_string(),
"for".to_string(),
"while".to_string(),
"match".to_string(),
],
nested_comments: true,
..Default::default()
}
}
#[test]
fn test_count_functions() {
let analyzer = ComplexityAnalyzer::new();
let lang = make_rust_lang();
let content = r#"
fn main() {
println!("hello");
}
pub fn helper() {}
pub async fn async_fn() {}
"#;
let complexity = analyzer.analyze(content, &lang);
assert_eq!(complexity.functions, 3);
}
#[test]
fn test_cyclomatic_complexity() {
let analyzer = ComplexityAnalyzer::new();
let lang = make_rust_lang();
let content = r#"
fn main() {
if true {
for i in 0..10 {
if i > 5 {
println!("{}", i);
}
}
} else {
while false {}
}
}
"#;
let complexity = analyzer.analyze(content, &lang);
assert_eq!(complexity.cyclomatic, 6);
}
#[test]
fn test_max_depth() {
let analyzer = ComplexityAnalyzer::new();
let lang = make_rust_lang();
let content = r#"
fn main() {
if true {
for i in 0..10 {
match i {
_ => {}
}
}
}
}
"#;
let complexity = analyzer.analyze(content, &lang);
assert!(complexity.max_depth >= 4);
}
}