scribe_analysis/language_support/
language_metrics.rs1use serde::{Deserialize, Serialize};
7use scribe_core::Result;
8use super::ast_language::AstLanguage;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct LanguageSpecificComplexity {
13 pub base_complexity: f64,
15 pub language_factors: f64,
17 pub idiom_score: f64,
19 pub framework_complexity: f64,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct LanguageMetrics {
26 pub language: AstLanguage,
28 pub lines_of_code: usize,
30 pub function_count: usize,
32 pub class_count: usize,
34 pub complexity: LanguageSpecificComplexity,
36 pub maintainability_score: f64,
38}
39
40impl LanguageMetrics {
41 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 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 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 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 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 if content.contains("async def") || content.contains("await ") {
143 language_factors += 0.3; }
145 if content.contains("@") {
146 language_factors += 0.2; }
148 if content.contains("[") && content.contains("for ") && content.contains("in ") {
149 language_factors += 0.1; }
151
152 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 if content.contains("'") && content.contains("&") {
164 language_factors += 0.4; }
166 if content.contains("match ") || content.contains("if let ") {
167 language_factors += 0.1; }
169 if content.contains("macro_rules!") || content.contains("!") {
170 language_factors += 0.3; }
172
173 if content.contains("Result<") || content.contains("Option<") {
175 idiom_score += 0.2; }
177 }
178
179 AstLanguage::JavaScript | AstLanguage::TypeScript => {
180 if content.contains("async ") || content.contains("await ") {
182 language_factors += 0.2; }
184 if content.contains("Promise") {
185 language_factors += 0.1; }
187 if language == AstLanguage::TypeScript {
188 if content.contains("<") && content.contains(">") {
189 language_factors += 0.2; }
191 }
192
193 if content.contains("import React") || content.contains("from 'react'") {
195 framework_complexity += 0.1;
196 }
197 }
198
199 AstLanguage::Go => {
200 if content.contains("go ") && content.contains("()") {
202 language_factors += 0.2; }
204 if content.contains("chan ") || content.contains("<-") {
205 language_factors += 0.3; }
207 if content.contains("defer ") {
208 language_factors += 0.1; }
210 }
211
212 _ => {
213 language_factors = 0.1;
215 }
216 }
217
218 let base_complexity = 1.0; LanguageSpecificComplexity {
221 base_complexity,
222 language_factors,
223 idiom_score,
224 framework_complexity,
225 }
226 }
227
228 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 if lines_of_code > 500 {
239 score -= (lines_of_code as f64 - 500.0) * 0.01;
240 }
241
242 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; } else if avg_lines_per_function > 100.0 {
248 score -= 10.0; }
250 }
251
252 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); }
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); }
309}