vtcode_core/code/code_quality/metrics/
complexity.rs1use std::fs;
2use std::path::Path;
3use walkdir::WalkDir;
4
5#[derive(Debug, Clone)]
7pub struct ComplexityResult {
8 pub cyclomatic_complexity: f64,
9 pub cognitive_complexity: f64,
10 pub lines_of_code: usize,
11 pub maintainability_index: f64,
12}
13
14pub struct ComplexityAnalyzer;
16
17impl ComplexityAnalyzer {
18 pub fn new() -> Self {
19 Self
20 }
21
22 pub fn analyze_file(&self, _file_path: &Path, source: &str) -> ComplexityResult {
24 let lines_of_code = source.lines().count();
26
27 let cyclomatic_complexity = self.calculate_cyclomatic_complexity(source);
29
30 let cognitive_complexity = self.calculate_cognitive_complexity(source);
32
33 let maintainability_index =
35 self.calculate_maintainability_index(cyclomatic_complexity, lines_of_code, source);
36
37 ComplexityResult {
38 cyclomatic_complexity,
39 cognitive_complexity,
40 lines_of_code,
41 maintainability_index,
42 }
43 }
44
45 pub fn analyze_directory(&self, dir_path: &Path) -> Vec<ComplexityResult> {
47 let mut results = Vec::new();
48
49 for entry in WalkDir::new(dir_path)
51 .follow_links(true)
52 .into_iter()
53 .filter_map(|e| e.ok())
54 {
55 if entry.file_type().is_file() {
56 if let Some(ext) = entry.path().extension() {
57 let source_extensions = ["rs", "js", "ts", "py", "java", "cpp", "c", "go"];
59 if source_extensions.contains(&ext.to_str().unwrap_or("")) {
60 if let Ok(content) = fs::read_to_string(entry.path()) {
61 let result = self.analyze_file(entry.path(), &content);
62 results.push(result);
63 }
64 }
65 }
66 }
67 }
68
69 results
70 }
71
72 fn calculate_cyclomatic_complexity(&self, source: &str) -> f64 {
74 let mut complexity = 1.0; for line in source.lines() {
78 let trimmed = line.trim();
79 if trimmed.starts_with("if ")
80 || trimmed.starts_with("while ")
81 || trimmed.starts_with("for ")
82 || trimmed.starts_with("match ")
83 || trimmed.contains(" && ")
84 || trimmed.contains(" || ")
85 || trimmed.starts_with("case ")
86 || trimmed.starts_with("catch ")
87 {
88 complexity += 1.0;
89 }
90 }
91
92 complexity
93 }
94
95 fn calculate_cognitive_complexity(&self, source: &str) -> f64 {
97 let mut complexity = 0.0;
98
99 let mut nesting_level: i32 = 0;
101 for line in source.lines() {
102 let trimmed = line.trim();
103
104 if trimmed.starts_with("if ")
106 || trimmed.starts_with("while ")
107 || trimmed.starts_with("for ")
108 || trimmed.starts_with("match ")
109 {
110 nesting_level += 1;
111 complexity += nesting_level as f64;
112 }
113
114 if trimmed == "}" || trimmed == "end" {
116 nesting_level = nesting_level.saturating_sub(1);
117 }
118 }
119
120 complexity
121 }
122
123 fn calculate_maintainability_index(
125 &self,
126 cyclomatic_complexity: f64,
127 lines_of_code: usize,
128 source: &str,
129 ) -> f64 {
130 let halstead_volume = source.chars().count() as f64;
132
133 let mi = 171.0
135 - 5.2 * halstead_volume.ln()
136 - 0.23 * cyclomatic_complexity
137 - 16.2 * (lines_of_code as f64).ln();
138
139 mi.max(0.0).min(100.0)
141 }
142}