1use anyhow::Result;
4use regex::Regex;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
9pub struct MetaclassInfo {
10 pub name: String,
11 pub metaclass_type: String,
12 pub impact: String,
13 pub attributes_modified: Vec<String>,
14 pub methods_modified: Vec<String>,
15}
16
17#[derive(Debug, Clone)]
19pub struct DecoratorInfo {
20 pub name: String,
21 pub decorator_type: String,
22 pub framework: Option<String>,
23 pub effects: Vec<String>,
24 pub is_factory: bool,
25 pub parameters: Vec<String>,
26}
27
28#[derive(Debug, Clone)]
30pub struct InheritanceInfo {
31 pub class_name: String,
32 pub base_classes: Vec<String>,
33 pub mro: Vec<String>,
34 pub has_diamond_inheritance: bool,
35 pub mixins: Vec<String>,
36 pub metaclass: Option<String>,
37}
38
39pub struct PythonAnalyzer {
41 decorator_patterns: HashMap<String, Vec<DecoratorPattern>>,
42 metaclass_patterns: HashMap<String, Vec<MetaclassPattern>>,
43}
44
45#[derive(Debug, Clone)]
46struct DecoratorPattern {
47 name: String,
48 pattern: Regex,
49 framework: Option<String>,
50 effects: Vec<String>,
51 is_factory: bool,
52}
53
54#[derive(Debug, Clone)]
55struct MetaclassPattern {
56 #[allow(dead_code)]
57 name: String,
58 pattern: Regex,
59 impact: String,
60}
61
62impl PythonAnalyzer {
63 pub fn new() -> Self {
64 let mut analyzer = Self {
65 decorator_patterns: HashMap::new(),
66 metaclass_patterns: HashMap::new(),
67 };
68 analyzer.initialize_patterns();
69 analyzer
70 }
71
72 fn initialize_patterns(&mut self) {
73 let framework_decorators = vec![
75 DecoratorPattern {
76 name: "Flask Route".to_string(),
77 pattern: Regex::new(r"@app\.route\s*\([^)]*\)").unwrap(),
78 framework: Some("Flask".to_string()),
79 effects: vec!["URL routing".to_string(), "HTTP method binding".to_string()],
80 is_factory: true,
81 },
82 DecoratorPattern {
83 name: "Django View".to_string(),
84 pattern: Regex::new(r"@(?:login_required|permission_required|csrf_exempt)")
85 .unwrap(),
86 framework: Some("Django".to_string()),
87 effects: vec!["Authentication".to_string(), "Authorization".to_string()],
88 is_factory: false,
89 },
90 DecoratorPattern {
91 name: "FastAPI Route".to_string(),
92 pattern: Regex::new(r"@app\.(?:get|post|put|delete|patch)\s*\([^)]*\)").unwrap(),
93 framework: Some("FastAPI".to_string()),
94 effects: vec!["API endpoint".to_string(), "Request validation".to_string()],
95 is_factory: true,
96 },
97 DecoratorPattern {
98 name: "Pytest Fixture".to_string(),
99 pattern: Regex::new(r"@pytest\.fixture\s*(?:\([^)]*\))?").unwrap(),
100 framework: Some("pytest".to_string()),
101 effects: vec!["Test setup".to_string(), "Dependency injection".to_string()],
102 is_factory: true,
103 },
104 DecoratorPattern {
105 name: "SQLAlchemy Event".to_string(),
106 pattern: Regex::new(r"@event\.listens_for\s*\([^)]*\)").unwrap(),
107 framework: Some("SQLAlchemy".to_string()),
108 effects: vec!["Database event handling".to_string()],
109 is_factory: true,
110 },
111 DecoratorPattern {
112 name: "Celery Task".to_string(),
113 pattern: Regex::new(r"@(?:celery\.)?task\s*(?:\([^)]*\))?").unwrap(),
114 framework: Some("Celery".to_string()),
115 effects: vec![
116 "Async task execution".to_string(),
117 "Queue processing".to_string(),
118 ],
119 is_factory: true,
120 },
121 ];
122 self.decorator_patterns
123 .insert("framework".to_string(), framework_decorators);
124
125 let pattern_decorators = vec![
127 DecoratorPattern {
128 name: "Caching Decorator".to_string(),
129 pattern: Regex::new(r"@(?:cache|lru_cache|memoize)").unwrap(),
130 framework: None,
131 effects: vec![
132 "Result caching".to_string(),
133 "Performance optimization".to_string(),
134 ],
135 is_factory: false,
136 },
137 DecoratorPattern {
138 name: "Validation Decorator".to_string(),
139 pattern: Regex::new(r"@(?:validate|validator|check)").unwrap(),
140 framework: None,
141 effects: vec!["Input validation".to_string(), "Type checking".to_string()],
142 is_factory: false,
143 },
144 DecoratorPattern {
145 name: "Authorization Decorator".to_string(),
146 pattern: Regex::new(r"@(?:requires_auth|authorized|permission)").unwrap(),
147 framework: None,
148 effects: vec![
149 "Access control".to_string(),
150 "Security enforcement".to_string(),
151 ],
152 is_factory: false,
153 },
154 DecoratorPattern {
155 name: "Logging Decorator".to_string(),
156 pattern: Regex::new(r"@(?:log|trace|audit)").unwrap(),
157 framework: None,
158 effects: vec![
159 "Function logging".to_string(),
160 "Execution tracing".to_string(),
161 ],
162 is_factory: false,
163 },
164 ];
165 self.decorator_patterns
166 .insert("patterns".to_string(), pattern_decorators);
167
168 let metaclass_patterns = vec![
170 MetaclassPattern {
171 name: "Registry Metaclass".to_string(),
172 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Registry\w*")
173 .unwrap(),
174 impact: "Automatic class registration".to_string(),
175 },
176 MetaclassPattern {
177 name: "Singleton Metaclass".to_string(),
178 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Singleton\w*")
179 .unwrap(),
180 impact: "Single instance enforcement".to_string(),
181 },
182 MetaclassPattern {
183 name: "Attribute Injection Metaclass".to_string(),
184 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Inject\w*").unwrap(),
185 impact: "Dynamic attribute injection".to_string(),
186 },
187 MetaclassPattern {
188 name: "ORM Metaclass".to_string(),
189 pattern: Regex::new(r"class\s+\w+\s*\([^)]*metaclass\s*=\s*\w*Model\w*").unwrap(),
190 impact: "Database mapping and validation".to_string(),
191 },
192 ];
193 self.metaclass_patterns
194 .insert("common".to_string(), metaclass_patterns);
195 }
196
197 pub fn analyze_decorators(&self, content: &str) -> Result<Vec<DecoratorInfo>> {
199 let mut decorators = Vec::new();
200
201 for (category, patterns) in &self.decorator_patterns {
202 for pattern in patterns {
203 for captures in pattern.pattern.captures_iter(content) {
204 let full_match = captures.get(0).unwrap().as_str();
205
206 decorators.push(DecoratorInfo {
207 name: pattern.name.clone(),
208 decorator_type: category.clone(),
209 framework: pattern.framework.clone(),
210 effects: pattern.effects.clone(),
211 is_factory: pattern.is_factory,
212 parameters: self.extract_decorator_parameters(full_match),
213 });
214 }
215 }
216 }
217
218 Ok(decorators)
219 }
220
221 pub fn analyze_metaclasses(&self, content: &str) -> Result<Vec<MetaclassInfo>> {
223 let mut metaclasses = Vec::new();
224
225 for (category, patterns) in &self.metaclass_patterns {
226 for pattern in patterns {
227 for captures in pattern.pattern.captures_iter(content) {
228 let full_match = captures.get(0).unwrap().as_str();
229 let class_name = self.extract_class_name(full_match);
230
231 metaclasses.push(MetaclassInfo {
232 name: class_name,
233 metaclass_type: category.clone(),
234 impact: pattern.impact.clone(),
235 attributes_modified: self.find_modified_attributes(content, full_match),
236 methods_modified: self.find_modified_methods(content, full_match),
237 });
238 }
239 }
240 }
241
242 Ok(metaclasses)
243 }
244
245 pub fn analyze_inheritance(&self, content: &str) -> Result<Vec<InheritanceInfo>> {
247 let mut inheritance_info = Vec::new();
248
249 let class_pattern = Regex::new(r"class\s+(\w+)\s*\(([^)]*)\)").unwrap();
250
251 for captures in class_pattern.captures_iter(content) {
252 let class_name = captures.get(1).unwrap().as_str().to_string();
253 let bases_str = captures.get(2).unwrap().as_str();
254
255 let base_classes = self.parse_base_classes(bases_str);
256 let mro = self.calculate_mro(&class_name, &base_classes);
257 let has_diamond = self.detect_diamond_inheritance(&base_classes);
258 let mixins = self.identify_mixins(&base_classes);
259 let metaclass = self.extract_metaclass(bases_str);
260
261 inheritance_info.push(InheritanceInfo {
262 class_name,
263 base_classes,
264 mro,
265 has_diamond_inheritance: has_diamond,
266 mixins,
267 metaclass,
268 });
269 }
270
271 Ok(inheritance_info)
272 }
273
274 fn extract_decorator_parameters(&self, decorator: &str) -> Vec<String> {
276 let param_pattern = Regex::new(r"\(([^)]*)\)").unwrap();
277
278 if let Some(captures) = param_pattern.captures(decorator) {
279 let params_str = captures.get(1).unwrap().as_str();
280 params_str
281 .split(',')
282 .map(|p| p.trim().to_string())
283 .filter(|p| !p.is_empty())
284 .collect()
285 } else {
286 Vec::new()
287 }
288 }
289
290 fn extract_class_name(&self, class_def: &str) -> String {
292 let name_pattern = Regex::new(r"class\s+(\w+)").unwrap();
293
294 if let Some(captures) = name_pattern.captures(class_def) {
295 captures.get(1).unwrap().as_str().to_string()
296 } else {
297 "Unknown".to_string()
298 }
299 }
300
301 fn find_modified_attributes(&self, _content: &str, _class_def: &str) -> Vec<String> {
303 vec!["__new__".to_string(), "__init__".to_string()]
305 }
306
307 fn find_modified_methods(&self, _content: &str, _class_def: &str) -> Vec<String> {
309 vec!["__call__".to_string()]
311 }
312
313 fn parse_base_classes(&self, bases_str: &str) -> Vec<String> {
315 bases_str
316 .split(',')
317 .map(|base| {
318 let clean_base = base.split('=').next().unwrap_or(base).trim();
320 clean_base.to_string()
321 })
322 .filter(|base| !base.is_empty() && !base.contains("metaclass"))
323 .collect()
324 }
325
326 fn calculate_mro(&self, class_name: &str, base_classes: &[String]) -> Vec<String> {
328 let mut mro = vec![class_name.to_string()];
329 mro.extend(base_classes.iter().cloned());
330 mro.push("object".to_string());
331 mro
332 }
333
334 fn detect_diamond_inheritance(&self, base_classes: &[String]) -> bool {
336 base_classes.len() > 1
338 }
339
340 fn identify_mixins(&self, base_classes: &[String]) -> Vec<String> {
342 base_classes
343 .iter()
344 .filter(|base| base.ends_with("Mixin") || base.ends_with("Mix"))
345 .cloned()
346 .collect()
347 }
348
349 fn extract_metaclass(&self, bases_str: &str) -> Option<String> {
351 let metaclass_pattern = Regex::new(r"metaclass\s*=\s*(\w+)").unwrap();
352
353 metaclass_pattern
354 .captures(bases_str)
355 .map(|captures| captures.get(1).unwrap().as_str().to_string())
356 }
357
358 pub fn get_python_recommendations(
360 &self,
361 decorators: &[DecoratorInfo],
362 metaclasses: &[MetaclassInfo],
363 inheritance: &[InheritanceInfo],
364 ) -> Vec<String> {
365 let mut recommendations = Vec::new();
366
367 let framework_decorators = decorators.iter().filter(|d| d.framework.is_some()).count();
369 if framework_decorators > 0 {
370 recommendations
371 .push("Consider documenting framework-specific decorator behavior.".to_string());
372 }
373
374 let factory_decorators = decorators.iter().filter(|d| d.is_factory).count();
375 if factory_decorators > 0 {
376 recommendations.push(
377 "Factory decorators detected - ensure proper parameter validation.".to_string(),
378 );
379 }
380
381 if !metaclasses.is_empty() {
383 recommendations.push(
384 "Metaclasses detected - document their behavior and impact on subclasses."
385 .to_string(),
386 );
387 recommendations.push(
388 "Consider if metaclass functionality could be achieved with simpler patterns."
389 .to_string(),
390 );
391 }
392
393 let diamond_inheritance = inheritance
395 .iter()
396 .filter(|i| i.has_diamond_inheritance)
397 .count();
398 if diamond_inheritance > 0 {
399 recommendations.push(
400 "Diamond inheritance detected - verify MRO behavior is as expected.".to_string(),
401 );
402 }
403
404 let complex_inheritance = inheritance
405 .iter()
406 .filter(|i| i.base_classes.len() > 2)
407 .count();
408 if complex_inheritance > 0 {
409 recommendations.push(
410 "Complex inheritance hierarchies detected - consider composition over inheritance."
411 .to_string(),
412 );
413 }
414
415 recommendations
416 .push("Use type hints for better code documentation and IDE support.".to_string());
417 recommendations.push("Consider using dataclasses for simple data containers.".to_string());
418
419 recommendations
420 }
421}
422
423impl Default for PythonAnalyzer {
424 fn default() -> Self {
425 Self::new()
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[test]
434 fn test_decorator_analysis() {
435 let analyzer = PythonAnalyzer::new();
436
437 let code = "@app.route('/test')\ndef test_view():\n pass";
438 let decorators = analyzer.analyze_decorators(code).unwrap();
439
440 assert!(!decorators.is_empty());
441 assert!(decorators.iter().any(|d| d.name == "Flask Route"));
442 }
443
444 #[test]
445 fn test_metaclass_analysis() {
446 let analyzer = PythonAnalyzer::new();
447
448 let code = "class TestClass(BaseClass, metaclass=RegistryMeta):\n pass";
449 let metaclasses = analyzer.analyze_metaclasses(code).unwrap();
450
451 assert!(!metaclasses.is_empty());
452 assert!(metaclasses.iter().any(|m| m.name == "TestClass"));
453 }
454
455 #[test]
456 fn test_inheritance_analysis() {
457 let analyzer = PythonAnalyzer::new();
458
459 let code = "class Child(Parent1, Parent2):\n pass";
460 let inheritance = analyzer.analyze_inheritance(code).unwrap();
461
462 assert!(!inheritance.is_empty());
463 assert_eq!(inheritance[0].class_name, "Child");
464 assert_eq!(inheritance[0].base_classes.len(), 2);
465 }
466
467 #[test]
468 fn test_decorator_parameter_extraction() {
469 let analyzer = PythonAnalyzer::new();
470
471 let decorator = "@app.route('/test', methods=['GET', 'POST'])";
472 let params = analyzer.extract_decorator_parameters(decorator);
473
474 assert!(!params.is_empty());
475 }
476
477 #[test]
478 fn test_diamond_inheritance_detection() {
479 let analyzer = PythonAnalyzer::new();
480
481 let base_classes = vec!["Parent1".to_string(), "Parent2".to_string()];
482 assert!(analyzer.detect_diamond_inheritance(&base_classes));
483
484 let single_base = vec!["Parent".to_string()];
485 assert!(!analyzer.detect_diamond_inheritance(&single_base));
486 }
487
488 #[test]
489 fn test_mixin_identification() {
490 let analyzer = PythonAnalyzer::new();
491
492 let base_classes = vec![
493 "BaseMixin".to_string(),
494 "RegularClass".to_string(),
495 "UtilMix".to_string(),
496 ];
497 let mixins = analyzer.identify_mixins(&base_classes);
498
499 assert_eq!(mixins.len(), 2);
500 assert!(mixins.contains(&"BaseMixin".to_string()));
501 assert!(mixins.contains(&"UtilMix".to_string()));
502 }
503}