ricecoder_refactoring/adapters/
python.rs

1//! Python-specific refactoring provider
2
3use crate::error::Result;
4use crate::providers::{RefactoringAnalysis, RefactoringProvider};
5use crate::types::{Refactoring, RefactoringType, ValidationResult};
6use regex::Regex;
7
8/// Python-specific refactoring provider
9pub struct PythonRefactoringProvider;
10
11impl PythonRefactoringProvider {
12    /// Create a new Python provider
13    pub fn new() -> Self {
14        Self
15    }
16
17    /// Check if code is valid Python
18    fn is_valid_python(code: &str) -> bool {
19        // Basic checks for Python syntax
20        let open_parens = code.matches('(').count();
21        let close_parens = code.matches(')').count();
22        let open_brackets = code.matches('[').count();
23        let close_brackets = code.matches(']').count();
24        let open_braces = code.matches('{').count();
25        let close_braces = code.matches('}').count();
26
27        open_parens == close_parens
28            && open_brackets == close_brackets
29            && open_braces == close_braces
30    }
31
32    /// Apply a Python-specific rename with word boundaries
33    pub fn apply_python_rename(code: &str, old_name: &str, new_name: &str) -> Result<String> {
34        let pattern = format!(r"\b{}\b", regex::escape(old_name));
35        match Regex::new(&pattern) {
36            Ok(re) => Ok(re.replace_all(code, new_name).to_string()),
37            Err(_) => Ok(code.replace(old_name, new_name)),
38        }
39    }
40
41    /// Check for Python code quality issues
42    pub fn check_python_quality(code: &str) -> Vec<String> {
43        let mut issues = vec![];
44
45        if code.contains("eval(") {
46            issues.push("Code uses eval() which is a security risk".to_string());
47        }
48
49        if code.contains("exec(") {
50            issues.push("Code uses exec() which is a security risk".to_string());
51        }
52
53        if code.contains("__import__") {
54            issues.push("Code uses __import__() for dynamic imports".to_string());
55        }
56
57        issues
58    }
59}
60
61impl Default for PythonRefactoringProvider {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67impl RefactoringProvider for PythonRefactoringProvider {
68    fn analyze_refactoring(
69        &self,
70        _code: &str,
71        _language: &str,
72        refactoring_type: RefactoringType,
73    ) -> Result<RefactoringAnalysis> {
74        let complexity = match refactoring_type {
75            RefactoringType::Rename => 3,
76            RefactoringType::Extract => 5,
77            RefactoringType::Inline => 4,
78            RefactoringType::Move => 6,
79            RefactoringType::ChangeSignature => 7,
80            RefactoringType::RemoveUnused => 3,
81            RefactoringType::Simplify => 4,
82        };
83
84        Ok(RefactoringAnalysis {
85            applicable: true,
86            reason: None,
87            complexity,
88        })
89    }
90
91    fn apply_refactoring(
92        &self,
93        code: &str,
94        _language: &str,
95        refactoring: &Refactoring,
96    ) -> Result<String> {
97        // Apply Python-specific refactoring
98        match refactoring.refactoring_type {
99            RefactoringType::Rename => {
100                // Python rename: use word boundaries
101                Self::apply_python_rename(code, &refactoring.target.symbol, &refactoring.target.symbol)
102            }
103            _ => Ok(code.to_string()),
104        }
105    }
106
107    fn validate_refactoring(
108        &self,
109        original: &str,
110        refactored: &str,
111        _language: &str,
112    ) -> Result<ValidationResult> {
113        let mut errors = vec![];
114        let mut warnings = vec![];
115
116        // Check if refactored code is not empty
117        if refactored.is_empty() {
118            errors.push("Refactored code cannot be empty".to_string());
119        }
120
121        // Check if content changed
122        if original == refactored {
123            warnings.push("No changes were made".to_string());
124        }
125
126        // Check Python syntax validity
127        if !Self::is_valid_python(refactored) {
128            errors.push("Refactored code has syntax errors (paren/bracket mismatch)".to_string());
129        }
130
131        // Check for common Python issues
132        if refactored.contains("exec(") && !original.contains("exec(") {
133            warnings.push("Refactoring introduced exec() call".to_string());
134        }
135
136        Ok(ValidationResult {
137            passed: errors.is_empty(),
138            errors,
139            warnings,
140        })
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_python_provider_analyze() -> Result<()> {
150        let provider = PythonRefactoringProvider::new();
151        let analysis = provider.analyze_refactoring("def main():", "python", RefactoringType::Rename)?;
152
153        assert!(analysis.applicable);
154        assert_eq!(analysis.complexity, 3);
155
156        Ok(())
157    }
158
159    #[test]
160    fn test_python_provider_validate_valid() -> Result<()> {
161        let provider = PythonRefactoringProvider::new();
162        let result = provider.validate_refactoring("def main():", "def main():\n    print()", "python")?;
163
164        assert!(result.passed);
165
166        Ok(())
167    }
168
169    #[test]
170    fn test_python_provider_validate_invalid_parens() -> Result<()> {
171        let provider = PythonRefactoringProvider::new();
172        let result = provider.validate_refactoring("def main():", "def main(:", "python")?;
173
174        assert!(!result.passed);
175
176        Ok(())
177    }
178
179    #[test]
180    fn test_is_valid_python() {
181        assert!(PythonRefactoringProvider::is_valid_python("def main():"));
182        assert!(PythonRefactoringProvider::is_valid_python("x = [1, 2, 3]"));
183        assert!(!PythonRefactoringProvider::is_valid_python("def main("));
184        assert!(!PythonRefactoringProvider::is_valid_python("x = [1, 2, 3"));
185    }
186}