scribe_analysis/language_support/
language_metrics.rs

1//! # Language-Specific Metrics
2//!
3//! Calculates language-specific complexity and quality metrics that are
4//! tailored to each programming language's characteristics.
5
6use super::ast_language::AstLanguage;
7use scribe_core::Result;
8use serde::{Deserialize, Serialize};
9
10/// Language-specific complexity factors
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct LanguageSpecificComplexity {
13    /// Base complexity score
14    pub base_complexity: f64,
15    /// Language-specific factors (e.g., async/await, generics)
16    pub language_factors: f64,
17    /// Idiomatic patterns bonus/penalty
18    pub idiom_score: f64,
19    /// Framework/library usage complexity
20    pub framework_complexity: f64,
21}
22
23/// Comprehensive language metrics
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct LanguageMetrics {
26    /// Programming language
27    pub language: AstLanguage,
28    /// Lines of code
29    pub lines_of_code: usize,
30    /// Number of functions
31    pub function_count: usize,
32    /// Number of classes
33    pub class_count: usize,
34    /// Language-specific complexity
35    pub complexity: LanguageSpecificComplexity,
36    /// Estimated maintainability score
37    pub maintainability_score: f64,
38}
39
40impl LanguageMetrics {
41    /// Calculate language metrics for source code
42    pub fn calculate(content: &str, language: AstLanguage) -> Result<Self> {
43        let lines: Vec<&str> = content.lines().collect();
44        let lines_of_code = lines.len();
45
46        // Basic pattern counting - can be enhanced with AST analysis
47        let function_count = Self::count_functions(content, language);
48        let class_count = Self::count_classes(content, language);
49
50        let complexity = Self::calculate_language_complexity(content, language);
51        let maintainability_score = Self::calculate_maintainability(
52            lines_of_code,
53            function_count,
54            class_count,
55            &complexity,
56        );
57
58        Ok(Self {
59            language,
60            lines_of_code,
61            function_count,
62            class_count,
63            complexity,
64            maintainability_score,
65        })
66    }
67
68    /// Count functions using language-specific patterns
69    fn count_functions(content: &str, language: AstLanguage) -> usize {
70        match language {
71            AstLanguage::Python => content
72                .lines()
73                .filter(|line| {
74                    line.trim().starts_with("def ") || line.trim().starts_with("async def ")
75                })
76                .count(),
77            AstLanguage::JavaScript | AstLanguage::TypeScript => content
78                .lines()
79                .filter(|line| {
80                    let trimmed = line.trim();
81                    trimmed.starts_with("function ")
82                        || trimmed.contains("=> ")
83                        || trimmed.contains("function(")
84                })
85                .count(),
86            AstLanguage::Rust => content
87                .lines()
88                .filter(|line| line.trim().starts_with("fn ") || line.trim().starts_with("pub fn "))
89                .count(),
90            AstLanguage::Go => content
91                .lines()
92                .filter(|line| line.trim().starts_with("func "))
93                .count(),
94            _ => 0,
95        }
96    }
97
98    /// Count classes using language-specific patterns
99    fn count_classes(content: &str, language: AstLanguage) -> usize {
100        match language {
101            AstLanguage::Python => content
102                .lines()
103                .filter(|line| line.trim().starts_with("class "))
104                .count(),
105            AstLanguage::JavaScript | AstLanguage::TypeScript => content
106                .lines()
107                .filter(|line| line.trim().starts_with("class "))
108                .count(),
109            AstLanguage::Rust => content
110                .lines()
111                .filter(|line| {
112                    let trimmed = line.trim();
113                    trimmed.starts_with("struct ")
114                        || trimmed.starts_with("pub struct ")
115                        || trimmed.starts_with("enum ")
116                        || trimmed.starts_with("pub enum ")
117                })
118                .count(),
119            AstLanguage::Go => content
120                .lines()
121                .filter(|line| line.trim().starts_with("type ") && line.contains("struct"))
122                .count(),
123            _ => 0,
124        }
125    }
126
127    /// Calculate language-specific complexity factors
128    fn calculate_language_complexity(
129        content: &str,
130        language: AstLanguage,
131    ) -> LanguageSpecificComplexity {
132        let mut language_factors = 0.0;
133        let mut idiom_score = 0.0;
134        let mut framework_complexity = 0.0;
135
136        match language {
137            AstLanguage::Python => {
138                // Python-specific complexity factors
139                if content.contains("async def") || content.contains("await ") {
140                    language_factors += 0.3; // Async complexity
141                }
142                if content.contains("@") {
143                    language_factors += 0.2; // Decorators
144                }
145                if content.contains("[") && content.contains("for ") && content.contains("in ") {
146                    language_factors += 0.1; // List comprehensions
147                }
148
149                // Framework detection
150                if content.contains("import django") || content.contains("from django") {
151                    framework_complexity += 0.2;
152                }
153                if content.contains("import flask") || content.contains("from flask") {
154                    framework_complexity += 0.1;
155                }
156            }
157
158            AstLanguage::Rust => {
159                // Rust-specific complexity factors
160                if content.contains("'") && content.contains("&") {
161                    language_factors += 0.4; // Lifetimes and borrowing
162                }
163                if content.contains("match ") || content.contains("if let ") {
164                    language_factors += 0.1; // Pattern matching
165                }
166                if content.contains("macro_rules!") || content.contains("!") {
167                    language_factors += 0.3; // Macros
168                }
169
170                // Idiomatic Rust patterns
171                if content.contains("Result<") || content.contains("Option<") {
172                    idiom_score += 0.2; // Good error handling
173                }
174            }
175
176            AstLanguage::JavaScript | AstLanguage::TypeScript => {
177                // JS/TS complexity factors
178                if content.contains("async ") || content.contains("await ") {
179                    language_factors += 0.2; // Async/await
180                }
181                if content.contains("Promise") {
182                    language_factors += 0.1; // Promises
183                }
184                if language == AstLanguage::TypeScript {
185                    if content.contains("<") && content.contains(">") {
186                        language_factors += 0.2; // Generics
187                    }
188                }
189
190                // Framework detection
191                if content.contains("import React") || content.contains("from 'react'") {
192                    framework_complexity += 0.1;
193                }
194            }
195
196            AstLanguage::Go => {
197                // Go-specific complexity factors
198                if content.contains("go ") && content.contains("()") {
199                    language_factors += 0.2; // Goroutines
200                }
201                if content.contains("chan ") || content.contains("<-") {
202                    language_factors += 0.3; // Channels
203                }
204                if content.contains("defer ") {
205                    language_factors += 0.1; // Defer statements
206                }
207            }
208
209            _ => {
210                // Generic complexity assessment
211                language_factors = 0.1;
212            }
213        }
214
215        let base_complexity = 1.0; // Base complexity
216
217        LanguageSpecificComplexity {
218            base_complexity,
219            language_factors,
220            idiom_score,
221            framework_complexity,
222        }
223    }
224
225    /// Calculate maintainability score
226    fn calculate_maintainability(
227        lines_of_code: usize,
228        function_count: usize,
229        class_count: usize,
230        complexity: &LanguageSpecificComplexity,
231    ) -> f64 {
232        let mut score = 100.0;
233
234        // Penalize large files
235        if lines_of_code > 500 {
236            score -= (lines_of_code as f64 - 500.0) * 0.01;
237        }
238
239        // Reward modular code
240        if function_count > 0 {
241            let avg_lines_per_function = lines_of_code as f64 / function_count as f64;
242            if avg_lines_per_function < 20.0 {
243                score += 5.0; // Small functions are good
244            } else if avg_lines_per_function > 100.0 {
245                score -= 10.0; // Large functions are bad
246            }
247        }
248
249        // Adjust for language complexity
250        score -= complexity.language_factors * 10.0;
251        score += complexity.idiom_score * 5.0;
252        score -= complexity.framework_complexity * 5.0;
253
254        score.max(0.0).min(100.0)
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_python_metrics() {
264        let python_code = r#"
265def hello():
266    print("Hello")
267
268async def async_hello():
269    await some_async_function()
270
271class Calculator:
272    def add(self, a, b):
273        return a + b
274"#;
275
276        let metrics = LanguageMetrics::calculate(python_code, AstLanguage::Python).unwrap();
277        assert_eq!(metrics.language, AstLanguage::Python);
278        assert!(metrics.function_count >= 2);
279        assert_eq!(metrics.class_count, 1);
280        assert!(metrics.complexity.language_factors > 0.0); // Should detect async
281    }
282
283    #[test]
284    fn test_rust_metrics() {
285        let rust_code = r#"
286fn main() {
287    println!("Hello, world!");
288}
289
290struct Calculator {
291    value: f64,
292}
293
294impl Calculator {
295    fn new() -> Self {
296        Calculator { value: 0.0 }
297    }
298}
299"#;
300
301        let metrics = LanguageMetrics::calculate(rust_code, AstLanguage::Rust).unwrap();
302        assert_eq!(metrics.language, AstLanguage::Rust);
303        assert!(metrics.function_count >= 1);
304        assert!(metrics.class_count >= 1); // struct counts as class
305    }
306}