vtcode_core/tools/tree_sitter/
refactoring.rs

1//! Code refactoring capabilities using tree-sitter
2
3use crate::tools::tree_sitter::analyzer::{Position, SyntaxNode, SyntaxTree};
4use crate::tools::tree_sitter::languages::{SymbolInfo, SymbolKind};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Refactoring operation
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RefactoringOperation {
11    pub kind: RefactoringKind,
12    pub description: String,
13    pub changes: Vec<CodeChange>,
14    pub preview: Vec<String>,
15}
16
17/// Type of refactoring operation
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19pub enum RefactoringKind {
20    Rename,
21    ExtractFunction,
22    ExtractVariable,
23    InlineFunction,
24    MoveFunction,
25    ChangeSignature,
26    AddParameter,
27    RemoveParameter,
28    ReorderParameters,
29    AddDocumentation,
30    RemoveUnused,
31    SimplifyCondition,
32    ExtractConstant,
33}
34
35/// Code change specification
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct CodeChange {
38    pub file_path: String,
39    pub old_range: TextRange,
40    pub new_text: String,
41    pub description: String,
42}
43
44/// Text range specification
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct TextRange {
47    pub start: Position,
48    pub end: Position,
49}
50
51/// Refactoring result
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct RefactoringResult {
54    pub success: bool,
55    pub operations: Vec<RefactoringOperation>,
56    pub conflicts: Vec<RefactoringConflict>,
57    pub preview: String,
58}
59
60/// Refactoring conflict
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct RefactoringConflict {
63    pub kind: ConflictKind,
64    pub message: String,
65    pub position: Position,
66    pub suggestion: Option<String>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum ConflictKind {
71    NameConflict,
72    ReferenceConflict,
73    ScopeConflict,
74    TypeConflict,
75    ImportConflict,
76}
77
78/// Code refactoring engine
79pub struct RefactoringEngine {
80    #[allow(dead_code)]
81    analysis_cache: HashMap<String, SyntaxTree>,
82}
83
84impl RefactoringEngine {
85    pub fn new() -> Self {
86        Self {
87            analysis_cache: HashMap::new(),
88        }
89    }
90
91    /// Analyze refactoring possibilities for a symbol
92    pub fn analyze_refactoring_options(
93        &self,
94        symbol: &SymbolInfo,
95        tree: &SyntaxTree,
96    ) -> Vec<RefactoringOperation> {
97        let mut operations = Vec::new();
98
99        match &symbol.kind {
100            SymbolKind::Function | SymbolKind::Method => {
101                operations.extend(self.analyze_function_refactoring(symbol, tree));
102            }
103            SymbolKind::Variable => {
104                operations.extend(self.analyze_variable_refactoring(symbol, tree));
105            }
106            SymbolKind::Class | SymbolKind::Struct => {
107                operations.extend(self.analyze_class_refactoring(symbol, tree));
108            }
109            _ => {}
110        }
111
112        operations
113    }
114
115    /// Analyze function-specific refactoring options
116    fn analyze_function_refactoring(
117        &self,
118        symbol: &SymbolInfo,
119        _tree: &SyntaxTree,
120    ) -> Vec<RefactoringOperation> {
121        let mut operations = Vec::new();
122
123        // Extract function (if function is too long)
124        if let Some(signature) = &symbol.signature {
125            if signature.lines().count() > 20 {
126                operations.push(RefactoringOperation {
127                    kind: RefactoringKind::ExtractFunction,
128                    description: format!(
129                        "Extract parts of {} into separate functions",
130                        symbol.name
131                    ),
132                    changes: vec![], // Would be populated with actual changes
133                    preview: vec!["// Extracted function".to_string()],
134                });
135            }
136        }
137
138        // Rename function
139        operations.push(RefactoringOperation {
140            kind: RefactoringKind::Rename,
141            description: format!("Rename function {}", symbol.name),
142            changes: vec![],
143            preview: vec![format!("fn new_name(/* parameters */) {{ /* body */ }}")],
144        });
145
146        // Add documentation
147        if symbol.documentation.is_none() {
148            operations.push(RefactoringOperation {
149                kind: RefactoringKind::AddDocumentation,
150                description: format!("Add documentation to function {}", symbol.name),
151                changes: vec![],
152                preview: vec![
153                    format!("/// Function documentation"),
154                    format!("fn {}(/* parameters */) {{ /* body */ }}", symbol.name),
155                ],
156            });
157        }
158
159        operations
160    }
161
162    /// Analyze variable-specific refactoring options
163    fn analyze_variable_refactoring(
164        &self,
165        symbol: &SymbolInfo,
166        _tree: &SyntaxTree,
167    ) -> Vec<RefactoringOperation> {
168        let mut operations = Vec::new();
169
170        // Extract constant
171        operations.push(RefactoringOperation {
172            kind: RefactoringKind::ExtractConstant,
173            description: format!("Extract {} into a named constant", symbol.name),
174            changes: vec![],
175            preview: vec![format!(
176                "const {}: Type = value;",
177                symbol.name.to_uppercase()
178            )],
179        });
180
181        // Rename variable
182        operations.push(RefactoringOperation {
183            kind: RefactoringKind::Rename,
184            description: format!("Rename variable {}", symbol.name),
185            changes: vec![],
186            preview: vec![format!("let new_name = value;")],
187        });
188
189        operations
190    }
191
192    /// Analyze class/struct-specific refactoring options
193    fn analyze_class_refactoring(
194        &self,
195        symbol: &SymbolInfo,
196        _tree: &SyntaxTree,
197    ) -> Vec<RefactoringOperation> {
198        let mut operations = Vec::new();
199
200        // Rename class/struct
201        operations.push(RefactoringOperation {
202            kind: RefactoringKind::Rename,
203            description: format!("Rename {} {}", symbol.kind_str(), symbol.name),
204            changes: vec![],
205            preview: vec![
206                format!("struct NewName {{"),
207                format!("    // fields"),
208                format!("}}"),
209            ],
210        });
211
212        operations
213    }
214
215    /// Generate refactoring preview
216    pub fn generate_preview(&self, operation: &RefactoringOperation) -> String {
217        let mut preview = format!("=== {} ===\n", operation.description);
218        preview.push_str(&format!("Type: {:?}\n\n", operation.kind));
219
220        preview.push_str("Preview:\n");
221        for line in &operation.preview {
222            preview.push_str(&format!("  {}\n", line));
223        }
224
225        if !operation.changes.is_empty() {
226            preview.push_str("\nChanges:\n");
227            for change in &operation.changes {
228                preview.push_str(&format!(
229                    "  {}: {} -> {}\n",
230                    change.file_path,
231                    change.old_range.start.row,
232                    change.new_text.chars().take(50).collect::<String>()
233                ));
234            }
235        }
236
237        preview
238    }
239
240    /// Apply refactoring operation
241    pub fn apply_refactoring(
242        &mut self,
243        operation: &RefactoringOperation,
244    ) -> Result<RefactoringResult, RefactoringError> {
245        // Validate operation
246        let conflicts = self.validate_operation(operation)?;
247
248        if !conflicts.is_empty() {
249            return Ok(RefactoringResult {
250                success: false,
251                operations: vec![operation.clone()],
252                conflicts,
253                preview: self.generate_preview(operation),
254            });
255        }
256
257        // Apply changes
258        let mut applied_changes = Vec::new();
259        for change in &operation.changes {
260            self.apply_change(change)?;
261            applied_changes.push(change.clone());
262        }
263
264        Ok(RefactoringResult {
265            success: true,
266            operations: vec![operation.clone()],
267            conflicts: vec![],
268            preview: self.generate_preview(operation),
269        })
270    }
271
272    /// Validate refactoring operation
273    fn validate_operation(
274        &self,
275        operation: &RefactoringOperation,
276    ) -> Result<Vec<RefactoringConflict>, RefactoringError> {
277        let mut conflicts = Vec::new();
278
279        match operation.kind {
280            RefactoringKind::Rename => {
281                // Check for naming conflicts
282                conflicts.extend(self.check_naming_conflicts(operation));
283            }
284            RefactoringKind::ExtractFunction => {
285                // Check for scope conflicts
286                conflicts.extend(self.check_scope_conflicts(operation));
287            }
288            _ => {}
289        }
290
291        Ok(conflicts)
292    }
293
294    /// Check for naming conflicts
295    fn check_naming_conflicts(&self, operation: &RefactoringOperation) -> Vec<RefactoringConflict> {
296        let mut conflicts = Vec::new();
297
298        if operation.kind != RefactoringKind::Rename {
299            return conflicts;
300        }
301
302        if let Some(change) = operation.changes.first() {
303            if let Ok(content) = std::fs::read_to_string(&change.file_path) {
304                if let Ok(re) =
305                    regex::Regex::new(&format!(r"\b{}\b", regex::escape(&change.new_text)))
306                {
307                    for mat in re.find_iter(&content) {
308                        if mat.start() != change.old_range.start.byte_offset {
309                            conflicts.push(RefactoringConflict {
310                                kind: ConflictKind::NameConflict,
311                                message: format!(
312                                    "name '{}' already exists in {}",
313                                    change.new_text, change.file_path
314                                ),
315                                position: Position {
316                                    row: 0,
317                                    column: 0,
318                                    byte_offset: mat.start(),
319                                },
320                                suggestion: Some("choose a different name".to_string()),
321                            });
322                            break;
323                        }
324                    }
325                }
326            }
327        }
328
329        conflicts
330    }
331
332    /// Check for scope conflicts
333    fn check_scope_conflicts(&self, _operation: &RefactoringOperation) -> Vec<RefactoringConflict> {
334        // This would implement scope analysis
335        Vec::new()
336    }
337
338    /// Apply a single change
339    fn apply_change(&mut self, change: &CodeChange) -> Result<(), RefactoringError> {
340        if change.old_range.start.byte_offset > change.old_range.end.byte_offset {
341            return Err(RefactoringError::InvalidRange(format!(
342                "Invalid range: start > end in {}",
343                change.file_path
344            )));
345        }
346
347        let mut content = std::fs::read_to_string(&change.file_path)
348            .map_err(|e| RefactoringError::FileOperationError(e.to_string()))?;
349
350        let start = change.old_range.start.byte_offset;
351        let end = change.old_range.end.byte_offset;
352        if end > content.len() {
353            return Err(RefactoringError::InvalidRange(format!(
354                "range exceeds file length in {}",
355                change.file_path
356            )));
357        }
358
359        content.replace_range(start..end, &change.new_text);
360        std::fs::write(&change.file_path, content)
361            .map_err(|e| RefactoringError::FileOperationError(e.to_string()))?;
362
363        Ok(())
364    }
365}
366
367/// Refactoring error
368#[derive(Debug, thiserror::Error)]
369pub enum RefactoringError {
370    #[error("Invalid text range: {0}")]
371    InvalidRange(String),
372
373    #[error("File operation failed: {0}")]
374    FileOperationError(String),
375
376    #[error("Validation failed: {0}")]
377    ValidationError(String),
378
379    #[error("Conflict detected: {0}")]
380    ConflictError(String),
381}
382
383/// Refactoring utilities
384pub struct RefactoringUtils;
385
386impl RefactoringUtils {
387    /// Suggest function extraction opportunities
388    pub fn suggest_function_extraction(tree: &SyntaxTree) -> Vec<FunctionExtractionSuggestion> {
389        let mut suggestions = Vec::new();
390
391        // Find long functions that could be extracted
392        Self::analyze_long_functions(&tree.root, &mut suggestions);
393
394        suggestions
395    }
396
397    /// Suggest variable extraction opportunities
398    pub fn suggest_variable_extraction(tree: &SyntaxTree) -> Vec<VariableExtractionSuggestion> {
399        let mut suggestions = Vec::new();
400
401        // Find repeated expressions that could be extracted
402        Self::analyze_repeated_expressions(&tree.root, &mut suggestions);
403
404        suggestions
405    }
406
407    /// Suggest constant extraction opportunities
408    pub fn suggest_constant_extraction(tree: &SyntaxTree) -> Vec<ConstantExtractionSuggestion> {
409        let mut suggestions = Vec::new();
410
411        // Find magic numbers and strings that could be constants
412        Self::analyze_magic_values(&tree.root, &mut suggestions);
413
414        suggestions
415    }
416
417    fn analyze_long_functions(
418        node: &SyntaxNode,
419        suggestions: &mut Vec<FunctionExtractionSuggestion>,
420    ) {
421        if node.kind.contains("function") || node.kind.contains("method") {
422            // Check if function body is long
423            let body_length = Self::calculate_node_size(node);
424            if body_length > 50 {
425                // Arbitrary threshold
426                if let Some(name_node) = node
427                    .named_children
428                    .get("name")
429                    .and_then(|children| children.first())
430                {
431                    suggestions.push(FunctionExtractionSuggestion {
432                        function_name: name_node.text.clone(),
433                        position: name_node.start_position.clone(),
434                        body_size: body_length,
435                        suggestion:
436                            "Consider extracting parts of this function into smaller functions"
437                                .to_string(),
438                    });
439                }
440            }
441        }
442
443        for child in &node.children {
444            Self::analyze_long_functions(child, suggestions);
445        }
446    }
447
448    fn analyze_repeated_expressions(
449        node: &SyntaxNode,
450        suggestions: &mut Vec<VariableExtractionSuggestion>,
451    ) {
452        use std::collections::HashMap;
453        let mut expr_map: HashMap<String, (usize, Position)> = HashMap::new();
454
455        fn traverse(node: &SyntaxNode, map: &mut HashMap<String, (usize, Position)>) {
456            if node.kind.contains("expression") {
457                let entry = map
458                    .entry(node.text.clone())
459                    .or_insert((0, node.start_position.clone()));
460                entry.0 += 1;
461            }
462            for child in &node.children {
463                traverse(child, map);
464            }
465        }
466
467        traverse(node, &mut expr_map);
468
469        for (expr, (count, pos)) in expr_map {
470            if count > 1 {
471                suggestions.push(VariableExtractionSuggestion {
472                    expression: expr,
473                    position: pos,
474                    occurrences: count,
475                    suggestion: "Consider extracting this repeated expression into a variable"
476                        .to_string(),
477                });
478            }
479        }
480    }
481
482    fn analyze_magic_values(
483        node: &SyntaxNode,
484        suggestions: &mut Vec<ConstantExtractionSuggestion>,
485    ) {
486        fn traverse(node: &SyntaxNode, out: &mut Vec<ConstantExtractionSuggestion>) {
487            if node.kind.contains("number") || node.kind.contains("string") {
488                let val = node.text.trim();
489                if val != "0" && val != "1" && !val.is_empty() {
490                    let value_type = if node.kind.contains("string") {
491                        "string"
492                    } else {
493                        "number"
494                    };
495                    out.push(ConstantExtractionSuggestion {
496                        value: val.to_string(),
497                        position: node.start_position.clone(),
498                        value_type: value_type.to_string(),
499                        suggestion: "Consider extracting this literal into a constant".to_string(),
500                    });
501                }
502            }
503            for child in &node.children {
504                traverse(child, out);
505            }
506        }
507
508        traverse(node, suggestions);
509    }
510
511    fn calculate_node_size(node: &SyntaxNode) -> usize {
512        node.end_position.byte_offset - node.start_position.byte_offset
513    }
514}
515
516/// Function extraction suggestion
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct FunctionExtractionSuggestion {
519    pub function_name: String,
520    pub position: Position,
521    pub body_size: usize,
522    pub suggestion: String,
523}
524
525/// Variable extraction suggestion
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct VariableExtractionSuggestion {
528    pub expression: String,
529    pub position: Position,
530    pub occurrences: usize,
531    pub suggestion: String,
532}
533
534/// Constant extraction suggestion
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct ConstantExtractionSuggestion {
537    pub value: String,
538    pub position: Position,
539    pub value_type: String,
540    pub suggestion: String,
541}
542
543impl SymbolInfo {
544    fn kind_str(&self) -> &str {
545        match &self.kind {
546            SymbolKind::Function => "function",
547            SymbolKind::Method => "method",
548            SymbolKind::Class => "class",
549            SymbolKind::Struct => "struct",
550            SymbolKind::Variable => "variable",
551            _ => "symbol",
552        }
553    }
554}