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