1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
use super::super::types::{FunctionInfo, StructureInfo, StructureType, Visibility};
use super::LanguageAnalyzer;
use crate::utils::errors::Result;
/// Python language complexity analyzer
pub struct PythonAnalyzer;
impl PythonAnalyzer {
pub fn new() -> Self {
Self
}
/// Extract function name from Python function declaration
fn extract_function_name(&self, line: &str) -> Option<String> {
if let Some(start) = line.find("def ") {
let after_def = &line[start + 4..];
after_def
.find('(')
.map(|end| after_def[..end].trim().to_string())
} else {
None
}
}
/// Count complexity keywords in Python code
fn count_complexity_keywords(&self, line: &str) -> usize {
let keywords = [
"if", "elif", "while", "for", "and", "or", "except", "finally",
];
keywords
.iter()
.map(|&keyword| line.matches(keyword).count())
.sum()
}
/// Count cognitive complexity for Python code
fn count_cognitive_complexity(&self, line: &str, nesting_level: usize) -> usize {
let mut complexity = 0;
let nesting_multiplier = nesting_level.max(1);
// Basic control structures
if line.contains("if ") {
complexity += nesting_multiplier;
}
if line.contains("elif ") {
complexity += 1;
}
if line.contains("else:") {
complexity += 1;
}
if line.contains("while ") {
complexity += nesting_multiplier;
}
if line.contains("for ") {
complexity += nesting_multiplier;
}
if line.contains("try:") {
complexity += nesting_multiplier;
}
if line.contains("except") {
complexity += 1;
}
if line.contains("finally") {
complexity += 1;
}
// Logical operators
complexity += line.matches(" and ").count() * nesting_multiplier;
complexity += line.matches(" or ").count() * nesting_multiplier;
// Comprehensions add complexity
if line.contains(" for ") && (line.contains("[") || line.contains("{")) {
complexity += 1;
}
complexity
}
/// Count function parameters
fn count_function_parameters(&self, line: &str) -> usize {
if let Some(start) = line.find('(') {
if let Some(end) = line.rfind(')') {
if end > start {
let params_str = &line[start + 1..end];
if params_str.trim().is_empty() {
return 0;
}
// Simple parameter counting (split by comma)
let param_count = params_str.split(',').count();
// Adjust for common patterns
if params_str.contains("self") {
return param_count.saturating_sub(1);
}
return param_count;
}
}
}
0
}
/// Detect Python structure type and name
fn detect_structure(&self, line: &str) -> Option<(StructureType, String)> {
if line.starts_with("class ") {
if let Some(name) = self.extract_class_name(line) {
return Some((StructureType::Class, name));
}
}
// Python doesn't have interfaces, but we can detect ABC classes
if line.contains("ABC") && line.starts_with("class ") {
if let Some(name) = self.extract_class_name(line) {
return Some((StructureType::Interface, name));
}
}
None
}
/// Extract Python class name
fn extract_class_name(&self, line: &str) -> Option<String> {
if let Some(start) = line.find("class ") {
let after_class = &line[start + 6..];
let name_part = after_class.split('(').next()?.split(':').next()?.trim();
if !name_part.is_empty() {
Some(name_part.to_string())
} else {
None
}
} else {
None
}
}
}
impl LanguageAnalyzer for PythonAnalyzer {
fn analyze_functions(&self, lines: &[String]) -> Result<Vec<FunctionInfo>> {
let mut functions = Vec::new();
let mut current_function: Option<FunctionInfo> = None;
let mut function_indent = 0;
for (line_num, line) in lines.iter().enumerate() {
let trimmed = line.trim();
// Skip comments and empty lines
if trimmed.starts_with("#") || trimmed.is_empty() {
continue;
}
// Calculate indentation
let current_indent = line.len() - line.trim_start().len();
// Function declaration detection
if trimmed.starts_with("def ") {
if let Some(func_name) = self.extract_function_name(trimmed) {
// Save previous function if exists
if let Some(func) = current_function.take() {
functions.push(func);
}
current_function = Some(FunctionInfo {
name: func_name,
line_count: 0,
cyclomatic_complexity: 1, // Base complexity
cognitive_complexity: 1, // Base cognitive complexity
nesting_depth: 0,
parameter_count: 0,
return_path_count: 0,
start_line: line_num + 1,
end_line: line_num + 1,
is_method: trimmed.contains("self"),
parent_class: None,
local_variable_count: 0,
has_recursion: false,
has_exception_handling: false,
visibility: Visibility::Public,
});
function_indent = current_indent;
}
}
if let Some(ref mut func) = current_function {
// Check if we're still in the function
if current_indent <= function_indent
&& line_num > func.start_line - 1
&& !trimmed.is_empty()
{
// Function ended
functions.push(func.clone());
current_function = None;
continue;
}
if current_indent > function_indent {
func.line_count += 1;
func.end_line = line_num + 1;
// Calculate nesting depth
let relative_indent = (current_indent - function_indent) / 4; // Assuming 4-space indentation
func.nesting_depth = func.nesting_depth.max(relative_indent);
// Calculate cyclomatic complexity
func.cyclomatic_complexity += self.count_complexity_keywords(trimmed);
// Calculate cognitive complexity
func.cognitive_complexity +=
self.count_cognitive_complexity(trimmed, relative_indent);
// Count parameters
if trimmed.contains('(') && func.parameter_count == 0 {
func.parameter_count = self.count_function_parameters(trimmed);
}
// Count return paths
if trimmed.contains("return") {
func.return_path_count += 1;
}
// Check for recursion
if trimmed.contains(&func.name) && !trimmed.starts_with("def ") {
func.has_recursion = true;
}
// Check for exception handling
if trimmed.contains("try:")
|| trimmed.contains("except")
|| trimmed.contains("finally")
{
func.has_exception_handling = true;
}
}
}
}
// Add the last function if exists
if let Some(func) = current_function {
functions.push(func);
}
Ok(functions)
}
fn analyze_structures(&self, lines: &[String]) -> Result<Vec<StructureInfo>> {
let mut structures = Vec::new();
let mut current_structure: Option<StructureInfo> = None;
let mut structure_indent = 0;
for (line_num, line) in lines.iter().enumerate() {
let trimmed = line.trim();
// Skip comments and empty lines
if trimmed.starts_with("#") || trimmed.is_empty() {
continue;
}
// Calculate indentation
let current_indent = line.len() - line.trim_start().len();
// Structure declaration detection
if let Some((structure_type, name)) = self.detect_structure(trimmed) {
// Save previous structure if exists
if let Some(structure) = current_structure.take() {
structures.push(structure);
}
current_structure = Some(StructureInfo {
name,
structure_type,
line_count: 0,
start_line: line_num + 1,
end_line: line_num + 1,
methods: Vec::new(),
properties: 0,
visibility: Visibility::Public, // Python doesn't have strict visibility
inheritance_depth: 0,
interface_count: 0,
});
structure_indent = current_indent;
}
if let Some(ref mut structure) = current_structure {
// Check if we're still in the structure
if current_indent <= structure_indent
&& line_num > structure.start_line - 1
&& !trimmed.is_empty()
{
// Structure ended
structures.push(structure.clone());
current_structure = None;
continue;
}
if current_indent > structure_indent {
structure.line_count += 1;
structure.end_line = line_num + 1;
// Count properties (self.property assignments)
if trimmed.starts_with("self.") && trimmed.contains('=') {
structure.properties += 1;
}
}
}
}
// Add the last structure if exists
if let Some(structure) = current_structure {
structures.push(structure);
}
Ok(structures)
}
}
impl Default for PythonAnalyzer {
fn default() -> Self {
Self::new()
}
}