1use 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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct TextRange {
47 pub start: Position,
48 pub end: Position,
49}
50
51#[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#[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
78pub 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 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 fn analyze_function_refactoring(
117 &self,
118 symbol: &SymbolInfo,
119 _tree: &SyntaxTree,
120 ) -> Vec<RefactoringOperation> {
121 let mut operations = Vec::new();
122
123 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![], preview: vec!["// Extracted function".to_string()],
134 });
135 }
136 }
137
138 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 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 fn analyze_variable_refactoring(
164 &self,
165 symbol: &SymbolInfo,
166 _tree: &SyntaxTree,
167 ) -> Vec<RefactoringOperation> {
168 let mut operations = Vec::new();
169
170 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 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 fn analyze_class_refactoring(
194 &self,
195 symbol: &SymbolInfo,
196 _tree: &SyntaxTree,
197 ) -> Vec<RefactoringOperation> {
198 let mut operations = Vec::new();
199
200 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 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 pub fn apply_refactoring(
242 &mut self,
243 operation: &RefactoringOperation,
244 ) -> Result<RefactoringResult, RefactoringError> {
245 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 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 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 conflicts.extend(self.check_naming_conflicts(operation));
283 }
284 RefactoringKind::ExtractFunction => {
285 conflicts.extend(self.check_scope_conflicts(operation));
287 }
288 _ => {}
289 }
290
291 Ok(conflicts)
292 }
293
294 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 fn check_scope_conflicts(&self, _operation: &RefactoringOperation) -> Vec<RefactoringConflict> {
334 Vec::new()
336 }
337
338 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#[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
383pub struct RefactoringUtils;
385
386impl RefactoringUtils {
387 pub fn suggest_function_extraction(tree: &SyntaxTree) -> Vec<FunctionExtractionSuggestion> {
389 let mut suggestions = Vec::new();
390
391 Self::analyze_long_functions(&tree.root, &mut suggestions);
393
394 suggestions
395 }
396
397 pub fn suggest_variable_extraction(tree: &SyntaxTree) -> Vec<VariableExtractionSuggestion> {
399 let mut suggestions = Vec::new();
400
401 Self::analyze_repeated_expressions(&tree.root, &mut suggestions);
403
404 suggestions
405 }
406
407 pub fn suggest_constant_extraction(tree: &SyntaxTree) -> Vec<ConstantExtractionSuggestion> {
409 let mut suggestions = Vec::new();
410
411 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 let body_length = Self::calculate_node_size(node);
424 if body_length > 50 {
425 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#[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#[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#[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}