codeprism_lang_python/
analysis.rs

1//! Python-specific code analysis module
2
3use anyhow::Result;
4use regex::Regex;
5use std::collections::HashMap;
6
7/// Python metaclass information
8#[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/// Python decorator information
18#[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/// Python inheritance information
29#[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
39/// Python-specific analyzer
40pub 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        // Framework decorators
74        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        // Pattern decorators
126        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        // Metaclass patterns
169        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    /// Analyze Python decorators
198    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    /// Analyze Python metaclasses
222    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    /// Analyze Python inheritance
246    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    /// Extract decorator parameters
275    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    /// Extract class name from class definition
291    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    /// Find attributes modified by metaclass
302    fn find_modified_attributes(&self, _content: &str, _class_def: &str) -> Vec<String> {
303        // Simplified implementation - in practice, this would analyze the metaclass code
304        vec!["__new__".to_string(), "__init__".to_string()]
305    }
306
307    /// Find methods modified by metaclass
308    fn find_modified_methods(&self, _content: &str, _class_def: &str) -> Vec<String> {
309        // Simplified implementation - in practice, this would analyze the metaclass code
310        vec!["__call__".to_string()]
311    }
312
313    /// Parse base classes from inheritance declaration
314    fn parse_base_classes(&self, bases_str: &str) -> Vec<String> {
315        bases_str
316            .split(',')
317            .map(|base| {
318                // Remove metaclass and other keyword arguments
319                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    /// Calculate Method Resolution Order (simplified)
327    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    /// Detect diamond inheritance pattern
335    fn detect_diamond_inheritance(&self, base_classes: &[String]) -> bool {
336        // Simplified check - in practice, this would build the full inheritance graph
337        base_classes.len() > 1
338    }
339
340    /// Identify mixin classes
341    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    /// Extract metaclass from base classes
350    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    /// Get Python-specific recommendations
359    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        // Decorator recommendations
368        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        // Metaclass recommendations
382        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        // Inheritance recommendations
394        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}