scribe_analysis/language_support/
language_metrics.rs1use super::ast_language::AstLanguage;
7use scribe_core::Result;
8use serde::{Deserialize, Serialize};
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 => 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 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 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 if content.contains("async def") || content.contains("await ") {
140 language_factors += 0.3; }
142 if content.contains("@") {
143 language_factors += 0.2; }
145 if content.contains("[") && content.contains("for ") && content.contains("in ") {
146 language_factors += 0.1; }
148
149 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 if content.contains("'") && content.contains("&") {
161 language_factors += 0.4; }
163 if content.contains("match ") || content.contains("if let ") {
164 language_factors += 0.1; }
166 if content.contains("macro_rules!") || content.contains("!") {
167 language_factors += 0.3; }
169
170 if content.contains("Result<") || content.contains("Option<") {
172 idiom_score += 0.2; }
174 }
175
176 AstLanguage::JavaScript | AstLanguage::TypeScript => {
177 if content.contains("async ") || content.contains("await ") {
179 language_factors += 0.2; }
181 if content.contains("Promise") {
182 language_factors += 0.1; }
184 if language == AstLanguage::TypeScript {
185 if content.contains("<") && content.contains(">") {
186 language_factors += 0.2; }
188 }
189
190 if content.contains("import React") || content.contains("from 'react'") {
192 framework_complexity += 0.1;
193 }
194 }
195
196 AstLanguage::Go => {
197 if content.contains("go ") && content.contains("()") {
199 language_factors += 0.2; }
201 if content.contains("chan ") || content.contains("<-") {
202 language_factors += 0.3; }
204 if content.contains("defer ") {
205 language_factors += 0.1; }
207 }
208
209 _ => {
210 language_factors = 0.1;
212 }
213 }
214
215 let base_complexity = 1.0; LanguageSpecificComplexity {
218 base_complexity,
219 language_factors,
220 idiom_score,
221 framework_complexity,
222 }
223 }
224
225 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 if lines_of_code > 500 {
236 score -= (lines_of_code as f64 - 500.0) * 0.01;
237 }
238
239 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; } else if avg_lines_per_function > 100.0 {
245 score -= 10.0; }
247 }
248
249 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); }
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); }
306}