codelens_core/analyzer/
complexity.rs1use crate::language::Language;
4
5use super::stats::Complexity;
6
7pub struct ComplexityAnalyzer;
9
10impl ComplexityAnalyzer {
11 pub fn new() -> Self {
12 Self
13 }
14
15 pub fn analyze(&self, content: &str, lang: &Language) -> Complexity {
19 let mut complexity = Complexity::default();
20 let patterns = lang.complexity_patterns();
21
22 if let Some(ref re) = patterns.function_re {
24 complexity.functions = re.find_iter(content).count();
25 }
26
27 if let Some(ref re) = patterns.keywords_re {
29 complexity.cyclomatic = re.find_iter(content).count();
30 }
31
32 complexity.cyclomatic += complexity.functions;
34
35 complexity.max_depth = self.calculate_max_depth(content);
37
38 if complexity.functions > 0 {
40 let total_lines = content.lines().count();
41 complexity.avg_func_lines = total_lines as f64 / complexity.functions as f64;
42 }
43
44 complexity
45 }
46
47 fn calculate_max_depth(&self, content: &str) -> usize {
49 let mut max_depth: usize = 0;
50 let mut current_depth: usize = 0;
51
52 for c in content.chars() {
53 match c {
54 '{' | '(' | '[' => {
55 current_depth += 1;
56 max_depth = max_depth.max(current_depth);
57 }
58 '}' | ')' | ']' => {
59 current_depth = current_depth.saturating_sub(1);
60 }
61 _ => {}
62 }
63 }
64
65 max_depth
66 }
67}
68
69impl Default for ComplexityAnalyzer {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn make_rust_lang() -> Language {
80 Language {
81 name: "Rust".to_string(),
82 extensions: vec![".rs".to_string()],
83 line_comments: vec!["//".to_string()],
84 block_comments: vec![("/*".to_string(), "*/".to_string())],
85 function_pattern: Some(r"(?m)^\s*(pub\s+)?(async\s+)?fn\s+\w+".to_string()),
86 complexity_keywords: vec![
87 "if".to_string(),
88 "else".to_string(),
89 "for".to_string(),
90 "while".to_string(),
91 "match".to_string(),
92 ],
93 nested_comments: true,
94 ..Default::default()
95 }
96 }
97
98 #[test]
99 fn test_count_functions() {
100 let analyzer = ComplexityAnalyzer::new();
101 let lang = make_rust_lang();
102
103 let content = r#"
104fn main() {
105 println!("hello");
106}
107
108pub fn helper() {}
109
110pub async fn async_fn() {}
111"#;
112
113 let complexity = analyzer.analyze(content, &lang);
114 assert_eq!(complexity.functions, 3);
115 }
116
117 #[test]
118 fn test_cyclomatic_complexity() {
119 let analyzer = ComplexityAnalyzer::new();
120 let lang = make_rust_lang();
121
122 let content = r#"
123fn main() {
124 if true {
125 for i in 0..10 {
126 if i > 5 {
127 println!("{}", i);
128 }
129 }
130 } else {
131 while false {}
132 }
133}
134"#;
135
136 let complexity = analyzer.analyze(content, &lang);
137 assert_eq!(complexity.cyclomatic, 6);
139 }
140
141 #[test]
142 fn test_max_depth() {
143 let analyzer = ComplexityAnalyzer::new();
144 let lang = make_rust_lang();
145
146 let content = r#"
147fn main() {
148 if true {
149 for i in 0..10 {
150 match i {
151 _ => {}
152 }
153 }
154 }
155}
156"#;
157
158 let complexity = analyzer.analyze(content, &lang);
159 assert!(complexity.max_depth >= 4);
160 }
161}