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 serde::{Deserialize, Serialize};
7use scribe_core::Result;
8use super::ast_language::AstLanguage;
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 => {
72                content.lines()
73                    .filter(|line| line.trim().starts_with("def ") || line.trim().starts_with("async def "))
74                    .count()
75            }
76            AstLanguage::JavaScript | AstLanguage::TypeScript => {
77                content.lines()
78                    .filter(|line| {
79                        let trimmed = line.trim();
80                        trimmed.starts_with("function ") ||
81                        trimmed.contains("=> ") ||
82                        trimmed.contains("function(")
83                    })
84                    .count()
85            }
86            AstLanguage::Rust => {
87                content.lines()
88                    .filter(|line| line.trim().starts_with("fn ") || line.trim().starts_with("pub fn "))
89                    .count()
90            }
91            AstLanguage::Go => {
92                content.lines()
93                    .filter(|line| line.trim().starts_with("func "))
94                    .count()
95            }
96            _ => 0,
97        }
98    }
99    
100    /// Count classes using language-specific patterns
101    fn count_classes(content: &str, language: AstLanguage) -> usize {
102        match language {
103            AstLanguage::Python => {
104                content.lines()
105                    .filter(|line| line.trim().starts_with("class "))
106                    .count()
107            }
108            AstLanguage::JavaScript | AstLanguage::TypeScript => {
109                content.lines()
110                    .filter(|line| line.trim().starts_with("class "))
111                    .count()
112            }
113            AstLanguage::Rust => {
114                content.lines()
115                    .filter(|line| {
116                        let trimmed = line.trim();
117                        trimmed.starts_with("struct ") ||
118                        trimmed.starts_with("pub struct ") ||
119                        trimmed.starts_with("enum ") ||
120                        trimmed.starts_with("pub enum ")
121                    })
122                    .count()
123            }
124            AstLanguage::Go => {
125                content.lines()
126                    .filter(|line| line.trim().starts_with("type ") && line.contains("struct"))
127                    .count()
128            }
129            _ => 0,
130        }
131    }
132    
133    /// Calculate language-specific complexity factors
134    fn calculate_language_complexity(content: &str, language: AstLanguage) -> LanguageSpecificComplexity {
135        let mut language_factors = 0.0;
136        let mut idiom_score = 0.0;
137        let mut framework_complexity = 0.0;
138        
139        match language {
140            AstLanguage::Python => {
141                // Python-specific complexity factors
142                if content.contains("async def") || content.contains("await ") {
143                    language_factors += 0.3; // Async complexity
144                }
145                if content.contains("@") {
146                    language_factors += 0.2; // Decorators
147                }
148                if content.contains("[") && content.contains("for ") && content.contains("in ") {
149                    language_factors += 0.1; // List comprehensions
150                }
151                
152                // Framework detection
153                if content.contains("import django") || content.contains("from django") {
154                    framework_complexity += 0.2;
155                }
156                if content.contains("import flask") || content.contains("from flask") {
157                    framework_complexity += 0.1;
158                }
159            }
160            
161            AstLanguage::Rust => {
162                // Rust-specific complexity factors
163                if content.contains("'") && content.contains("&") {
164                    language_factors += 0.4; // Lifetimes and borrowing
165                }
166                if content.contains("match ") || content.contains("if let ") {
167                    language_factors += 0.1; // Pattern matching
168                }
169                if content.contains("macro_rules!") || content.contains("!") {
170                    language_factors += 0.3; // Macros
171                }
172                
173                // Idiomatic Rust patterns
174                if content.contains("Result<") || content.contains("Option<") {
175                    idiom_score += 0.2; // Good error handling
176                }
177            }
178            
179            AstLanguage::JavaScript | AstLanguage::TypeScript => {
180                // JS/TS complexity factors
181                if content.contains("async ") || content.contains("await ") {
182                    language_factors += 0.2; // Async/await
183                }
184                if content.contains("Promise") {
185                    language_factors += 0.1; // Promises
186                }
187                if language == AstLanguage::TypeScript {
188                    if content.contains("<") && content.contains(">") {
189                        language_factors += 0.2; // Generics
190                    }
191                }
192                
193                // Framework detection
194                if content.contains("import React") || content.contains("from 'react'") {
195                    framework_complexity += 0.1;
196                }
197            }
198            
199            AstLanguage::Go => {
200                // Go-specific complexity factors
201                if content.contains("go ") && content.contains("()") {
202                    language_factors += 0.2; // Goroutines
203                }
204                if content.contains("chan ") || content.contains("<-") {
205                    language_factors += 0.3; // Channels
206                }
207                if content.contains("defer ") {
208                    language_factors += 0.1; // Defer statements
209                }
210            }
211            
212            _ => {
213                // Generic complexity assessment
214                language_factors = 0.1;
215            }
216        }
217        
218        let base_complexity = 1.0; // Base complexity
219        
220        LanguageSpecificComplexity {
221            base_complexity,
222            language_factors,
223            idiom_score,
224            framework_complexity,
225        }
226    }
227    
228    /// Calculate maintainability score
229    fn calculate_maintainability(
230        lines_of_code: usize,
231        function_count: usize,
232        class_count: usize,
233        complexity: &LanguageSpecificComplexity,
234    ) -> f64 {
235        let mut score = 100.0;
236        
237        // Penalize large files
238        if lines_of_code > 500 {
239            score -= (lines_of_code as f64 - 500.0) * 0.01;
240        }
241        
242        // Reward modular code
243        if function_count > 0 {
244            let avg_lines_per_function = lines_of_code as f64 / function_count as f64;
245            if avg_lines_per_function < 20.0 {
246                score += 5.0; // Small functions are good
247            } else if avg_lines_per_function > 100.0 {
248                score -= 10.0; // Large functions are bad
249            }
250        }
251        
252        // Adjust for language complexity
253        score -= complexity.language_factors * 10.0;
254        score += complexity.idiom_score * 5.0;
255        score -= complexity.framework_complexity * 5.0;
256        
257        score.max(0.0).min(100.0)
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264    
265    #[test]
266    fn test_python_metrics() {
267        let python_code = r#"
268def hello():
269    print("Hello")
270
271async def async_hello():
272    await some_async_function()
273
274class Calculator:
275    def add(self, a, b):
276        return a + b
277"#;
278        
279        let metrics = LanguageMetrics::calculate(python_code, AstLanguage::Python).unwrap();
280        assert_eq!(metrics.language, AstLanguage::Python);
281        assert!(metrics.function_count >= 2);
282        assert_eq!(metrics.class_count, 1);
283        assert!(metrics.complexity.language_factors > 0.0); // Should detect async
284    }
285    
286    #[test]
287    fn test_rust_metrics() {
288        let rust_code = r#"
289fn main() {
290    println!("Hello, world!");
291}
292
293struct Calculator {
294    value: f64,
295}
296
297impl Calculator {
298    fn new() -> Self {
299        Calculator { value: 0.0 }
300    }
301}
302"#;
303        
304        let metrics = LanguageMetrics::calculate(rust_code, AstLanguage::Rust).unwrap();
305        assert_eq!(metrics.language, AstLanguage::Rust);
306        assert!(metrics.function_count >= 1);
307        assert!(metrics.class_count >= 1); // struct counts as class
308    }
309}