Skip to main content

vtcode_core/code/code_quality/metrics/
complexity.rs

1use std::fs;
2use std::path::Path;
3use vtcode_commons::walk::build_walker_single_threaded;
4
5/// Complexity analysis results
6#[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
14/// Complexity analyzer for code quality metrics
15pub struct ComplexityAnalyzer;
16
17impl ComplexityAnalyzer {
18    pub fn new() -> Self {
19        Self
20    }
21
22    /// Analyze complexity of a source file
23    pub fn analyze_file(&self, _file_path: &Path, source: &str) -> ComplexityResult {
24        // Calculate actual complexity metrics
25        let lines_of_code = source.lines().count();
26
27        // Calculate cyclomatic complexity (simplified)
28        let cyclomatic_complexity = self.calculate_cyclomatic_complexity(source);
29
30        // Calculate cognitive complexity (simplified)
31        let cognitive_complexity = self.calculate_cognitive_complexity(source);
32
33        // Calculate maintainability index
34        let maintainability_index = self.calculate_maintainability_index(cyclomatic_complexity, lines_of_code, source);
35
36        ComplexityResult {
37            cyclomatic_complexity,
38            cognitive_complexity,
39            lines_of_code,
40            maintainability_index,
41        }
42    }
43
44    /// Analyze complexity of a directory
45    pub fn analyze_directory(&self, dir_path: &Path) -> Vec<ComplexityResult> {
46        let mut results = Vec::new();
47
48        // Walk the directory to analyze all source files
49        for entry in build_walker_single_threaded(dir_path)
50            .follow_links(true)
51            .build()
52            .filter_map(|e| e.ok())
53        {
54            if entry.file_type().is_some_and(|ft| ft.is_file())
55                && let Some(ext) = entry.path().extension()
56            {
57                // Check if this is a source code file
58                let source_extensions = ["rs", "js", "ts", "py", "java", "cpp", "c", "go"];
59                if source_extensions.contains(&ext.to_str().unwrap_or(""))
60                    && let Ok(content) = fs::read_to_string(entry.path())
61                {
62                    let result = self.analyze_file(entry.path(), &content);
63                    results.push(result);
64                }
65            }
66        }
67
68        results
69    }
70
71    /// Calculate cyclomatic complexity (simplified implementation)
72    fn calculate_cyclomatic_complexity(&self, source: &str) -> f64 {
73        let mut complexity = 1.0; // Base complexity
74
75        // Count decision points
76        for line in source.lines() {
77            let trimmed = line.trim();
78            if trimmed.starts_with("if ")
79                || trimmed.starts_with("while ")
80                || trimmed.starts_with("for ")
81                || trimmed.starts_with("match ")
82                || trimmed.contains(" && ")
83                || trimmed.contains(" || ")
84                || trimmed.starts_with("case ")
85                || trimmed.starts_with("catch ")
86            {
87                complexity += 1.0;
88            }
89        }
90
91        complexity
92    }
93
94    /// Calculate cognitive complexity (simplified implementation)
95    fn calculate_cognitive_complexity(&self, source: &str) -> f64 {
96        let mut complexity = 0.0;
97
98        // Count nesting levels and complex structures
99        let mut nesting_level: i32 = 0;
100        for line in source.lines() {
101            let trimmed = line.trim();
102
103            // Increase nesting level for control structures
104            if trimmed.starts_with("if ")
105                || trimmed.starts_with("while ")
106                || trimmed.starts_with("for ")
107                || trimmed.starts_with("match ")
108            {
109                nesting_level += 1;
110                complexity += nesting_level as f64;
111            }
112
113            // Decrease nesting level for closing braces/keywords
114            if trimmed == "}" || trimmed == "end" {
115                nesting_level = nesting_level.saturating_sub(1);
116            }
117        }
118
119        complexity
120    }
121
122    /// Calculate maintainability index
123    fn calculate_maintainability_index(&self, cyclomatic_complexity: f64, lines_of_code: usize, source: &str) -> f64 {
124        // Count Halstead volume approximation (simplified)
125        let halstead_volume = source.chars().count() as f64;
126
127        // Maintainability index formula (simplified)
128        let mi = 171.0 - 5.2 * halstead_volume.ln() - 0.23 * cyclomatic_complexity - 16.2 * (lines_of_code as f64).ln();
129
130        // Normalize to 0-100 scale
131        mi.clamp(0.0, 100.0)
132    }
133}
134
135impl Default for ComplexityAnalyzer {
136    fn default() -> Self {
137        Self::new()
138    }
139}