rs_hack/
editor.rs

1use anyhow::{Context, Result};
2use proc_macro2::{LineColumn, Span};
3use syn::{
4    parse_str, File, Item, ItemEnum, ItemStruct,
5    Fields, Field, spanned::Spanned, Arm, ExprMatch, ExprStruct,
6    visit_mut::VisitMut, Expr,
7};
8use quote::ToTokens;
9
10use crate::operations::*;
11use prettyplease;
12
13pub struct RustEditor {
14    content: String,
15    syntax_tree: File,
16    line_offsets: Vec<usize>, // Byte offset for each line start
17}
18
19impl RustEditor {
20    pub fn new(content: &str) -> Result<Self> {
21        let syntax_tree: File = syn::parse_str(content)
22            .context("Failed to parse Rust code")?;
23
24        let line_offsets = Self::compute_line_offsets(content);
25
26        Ok(Self {
27            content: content.to_string(),
28            syntax_tree,
29            line_offsets,
30        })
31    }
32
33    /// Format a field without extra spaces (e.g., "pub name: String" not "pub name : String")
34    fn format_field(field: &Field) -> String {
35        let mut result = String::new();
36
37        // Add visibility
38        if let syn::Visibility::Public(_) = field.vis {
39            result.push_str("pub ");
40        }
41
42        // Add field name
43        if let Some(ident) = &field.ident {
44            result.push_str(&ident.to_string());
45        }
46
47        // Add colon and type (no space before colon)
48        result.push_str(": ");
49
50        // Format type without extra spaces
51        let type_str = field.ty.to_token_stream().to_string();
52        let type_str = type_str.replace(" < ", "<").replace(" >", ">");
53        result.push_str(&type_str);
54
55        result
56    }
57    
58    fn compute_line_offsets(content: &str) -> Vec<usize> {
59        let mut offsets = vec![0];
60        for (i, ch) in content.char_indices() {
61            if ch == '\n' {
62                offsets.push(i + 1);
63            }
64        }
65        offsets
66    }
67    
68    pub fn apply_operation(&mut self, op: &Operation) -> Result<ModificationResult> {
69        match op {
70            Operation::AddStructField(op) => self.add_struct_field(op),
71            Operation::UpdateStructField(op) => self.update_struct_field(op),
72            Operation::RemoveStructField(op) => self.remove_struct_field(op),
73            Operation::AddStructLiteralField(op) => self.add_struct_literal_field(op),
74            Operation::AddEnumVariant(op) => self.add_enum_variant(op),
75            Operation::UpdateEnumVariant(op) => self.update_enum_variant(op),
76            Operation::RemoveEnumVariant(op) => self.remove_enum_variant(op),
77            Operation::AddMatchArm(op) => self.add_match_arm(op),
78            Operation::UpdateMatchArm(op) => self.update_match_arm(op),
79            Operation::RemoveMatchArm(op) => self.remove_match_arm(op),
80            Operation::AddImplMethod(op) => self.add_impl_method(op),
81            Operation::AddUseStatement(op) => self.add_use_statement(op),
82            Operation::AddDerive(op) => self.add_derive(op),
83        }
84    }
85    
86    pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
87        // Find the struct and clone it to avoid borrowing issues
88        let item_struct = self.syntax_tree.items.iter()
89            .find_map(|item| {
90                if let Item::Struct(s) = item {
91                    if s.ident == op.struct_name {
92                        return Some(s.clone());
93                    }
94                }
95                None
96            })
97            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
98
99        // Check if the struct matches the where filter (if specified)
100        if let Some(ref where_filter) = op.where_filter {
101            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
102                // Struct doesn't match filter - skip without error
103                return Ok(ModificationResult {
104                    changed: false,
105                    modified_nodes: vec![],
106                });
107            }
108        }
109
110        // Create backup of original struct before modification
111        let backup_node = BackupNode {
112            node_type: "ItemStruct".to_string(),
113            identifier: op.struct_name.clone(),
114            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
115            location: self.span_to_location(item_struct.span()),
116        };
117
118        // First, insert the field into the struct definition
119        let modified = self.insert_struct_field(&item_struct, op)
120            .context("Failed to add field to struct definition")?;
121
122        if !modified {
123            return Ok(ModificationResult {
124                changed: false,
125                modified_nodes: vec![],
126            });
127        }
128
129        let mut modified_nodes = vec![backup_node];
130
131        // If literal_default is provided and struct field was added, also update struct literals
132        if let Some(ref literal_default) = op.literal_default {
133            // Re-parse the content to update syntax_tree with the struct field changes
134            self.syntax_tree = syn::parse_str(&self.content)
135                .context("Failed to re-parse content after adding struct field")?;
136            self.line_offsets = Self::compute_line_offsets(&self.content);
137
138            // Extract field name from field_def (e.g., "return_type: Option<Type>" -> "return_type")
139            let field_name = op.field_def.split(':')
140                .next()
141                .map(|s| s.trim().to_string())
142                .context("Failed to extract field name from field definition")?;
143
144            // Create the AddStructLiteralFieldOp
145            let literal_op = AddStructLiteralFieldOp {
146                struct_name: op.struct_name.clone(),
147                field_def: format!("{}: {}", field_name, literal_default),
148                position: op.position.clone(),
149            };
150
151            // Update all struct literals
152            let literal_result = self.add_struct_literal_field(&literal_op)
153                .context("Failed to update struct literals")?;
154            modified_nodes.extend(literal_result.modified_nodes);
155        }
156
157        Ok(ModificationResult {
158            changed: true,
159            modified_nodes,
160        })
161    }
162    
163    fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
164        if let Fields::Named(ref fields) = item_struct.fields {
165            // Parse the new field
166            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
167            let dummy: ItemStruct = parse_str(&field_code)
168                .context("Failed to parse field definition")?;
169
170            let new_field = if let Fields::Named(ref nf) = dummy.fields {
171                nf.named.first()
172                    .context("No field found in definition")?
173                    .clone()
174            } else {
175                anyhow::bail!("Expected named field");
176            };
177
178            // Check if field already exists
179            let new_field_name = new_field.ident.as_ref()
180                .map(|i| i.to_string())
181                .context("Field must have a name")?;
182
183            if fields.named.iter().any(|f| {
184                f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
185            }) {
186                // Field already exists, skip adding
187                return Ok(false);
188            }
189            
190            // Determine insertion point
191            let insert_pos = match &op.position {
192                InsertPosition::First => {
193                    if let Some(first_field) = fields.named.first() {
194                        self.span_to_byte_offset(first_field.span().start())
195                    } else {
196                        // Empty struct, insert after the opening brace
197                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
198                        brace_pos + 1
199                    }
200                }
201                InsertPosition::Last => {
202                    if let Some(last_field) = fields.named.last() {
203                        let end = self.span_to_byte_offset(last_field.span().end());
204                        // Find the comma or end
205                        self.find_after_field_end(end)
206                    } else {
207                        // Empty struct
208                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
209                        brace_pos + 1
210                    }
211                }
212                InsertPosition::After(name) => {
213                    let field = fields.named.iter()
214                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
215                        .with_context(|| format!("Field '{}' not found", name))?;
216                    let end = self.span_to_byte_offset(field.span().end());
217                    self.find_after_field_end(end)
218                }
219                InsertPosition::Before(name) => {
220                    let field = fields.named.iter()
221                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
222                        .with_context(|| format!("Field '{}' not found", name))?;
223                    self.span_to_byte_offset(field.span().start())
224                }
225            };
226            
227            // Format the new field
228            let indent = self.get_indentation(insert_pos);
229            let field_str = Self::format_field(&new_field);
230            let insert_text = if matches!(op.position, InsertPosition::First) {
231                format!("\n{}{},", indent, field_str)
232            } else {
233                format!("\n{}{},", indent, field_str)
234            };
235
236            self.content.insert_str(insert_pos, &insert_text);
237            return Ok(true);
238        }
239        
240        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
241    }
242
243    pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
244        // Find the struct and clone it to avoid borrowing issues
245        let item_struct = self.syntax_tree.items.iter()
246            .find_map(|item| {
247                if let Item::Struct(s) = item {
248                    if s.ident == op.struct_name {
249                        return Some(s.clone());
250                    }
251                }
252                None
253            })
254            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
255
256        // Check if the struct matches the where filter (if specified)
257        if let Some(ref where_filter) = op.where_filter {
258            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
259                // Struct doesn't match filter - skip without error
260                return Ok(ModificationResult {
261                    changed: false,
262                    modified_nodes: vec![],
263                });
264            }
265        }
266
267        // Create backup of original struct before modification
268        let backup_node = BackupNode {
269            node_type: "ItemStruct".to_string(),
270            identifier: op.struct_name.clone(),
271            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
272            location: self.span_to_location(item_struct.span()),
273        };
274
275        let modified = self.replace_struct_field(&item_struct, op)?;
276
277        Ok(ModificationResult {
278            changed: modified,
279            modified_nodes: if modified { vec![backup_node] } else { vec![] },
280        })
281    }
282
283    fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
284        if let Fields::Named(ref fields) = item_struct.fields {
285            // Parse the new field definition to get the field name
286            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
287            let dummy: ItemStruct = parse_str(&field_code)
288                .context("Failed to parse field definition")?;
289
290            let new_field = if let Fields::Named(ref nf) = dummy.fields {
291                nf.named.first()
292                    .context("No field found in definition")?
293                    .clone()
294            } else {
295                anyhow::bail!("Expected named field");
296            };
297
298            // Extract the field name from the parsed field
299            let field_name = new_field.ident.as_ref()
300                .map(|i| i.to_string())
301                .context("Field must have a name")?;
302
303            // Find the existing field
304            let existing_field = fields.named.iter()
305                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
306                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
307
308            // Get the span of the existing field
309            let start = self.span_to_byte_offset(existing_field.span().start());
310            let end = self.span_to_byte_offset(existing_field.span().end());
311
312            // Format and replace the field
313            let new_field_str = Self::format_field(&new_field);
314
315            // Remove the old field and insert the new one
316            self.content.replace_range(start..end, &new_field_str);
317
318            return Ok(true);
319        }
320
321        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
322    }
323
324    pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
325        // Find the struct and clone it to avoid borrowing issues
326        let item_struct = self.syntax_tree.items.iter()
327            .find_map(|item| {
328                if let Item::Struct(s) = item {
329                    if s.ident == op.struct_name {
330                        return Some(s.clone());
331                    }
332                }
333                None
334            })
335            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
336
337        // Check if the struct matches the where filter (if specified)
338        if let Some(ref where_filter) = op.where_filter {
339            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
340                // Struct doesn't match filter - skip without error
341                return Ok(ModificationResult {
342                    changed: false,
343                    modified_nodes: vec![],
344                });
345            }
346        }
347
348        // Create backup of original struct before modification
349        let backup_node = BackupNode {
350            node_type: "ItemStruct".to_string(),
351            identifier: op.struct_name.clone(),
352            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
353            location: self.span_to_location(item_struct.span()),
354        };
355
356        if let Fields::Named(ref fields) = item_struct.fields {
357            // Find the field to remove
358            let field_to_remove = fields.named.iter()
359                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
360                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
361
362            // Get the span including the comma
363            let start = self.span_to_byte_offset(field_to_remove.span().start());
364            let mut end = self.span_to_byte_offset(field_to_remove.span().end());
365
366            // Find and include the comma and any trailing whitespace/newline
367            while end < self.content.len() {
368                match self.content.as_bytes()[end] as char {
369                    ',' => {
370                        end += 1;
371                        // Also consume the newline after the comma if present
372                        if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
373                            end += 1;
374                        }
375                        break;
376                    }
377                    ' ' | '\t' => end += 1,
378                    '\n' => {
379                        end += 1;
380                        break;
381                    }
382                    _ => break,
383                }
384            }
385
386            // Also need to remove leading whitespace/indentation on the same line
387            let mut line_start = start;
388            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
389                line_start -= 1;
390            }
391
392            // Check if there's only whitespace between line_start and start
393            let before_field = &self.content[line_start..start];
394            if before_field.trim().is_empty() {
395                // Remove the whole line
396                self.content.replace_range(line_start..end, "");
397            } else {
398                // Just remove the field and comma
399                self.content.replace_range(start..end, "");
400            }
401
402            return Ok(ModificationResult {
403                changed: true,
404                modified_nodes: vec![backup_node],
405            });
406        }
407
408        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
409    }
410
411    pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
412        // Parse the field name from field_def (e.g., "return_type: None" -> "return_type")
413        let field_name = op.field_def.split(':')
414            .next()
415            .map(|s| s.trim().to_string())
416            .context("Field definition must contain ':'")?;
417
418        // Collect backups of all struct literal expressions that will be modified
419        let backup_nodes = self.collect_struct_literal_backups(&op.struct_name);
420
421        // Use a visitor to find and modify all struct literals
422        let mut visitor = StructLiteralFieldAdder {
423            struct_name: op.struct_name.clone(),
424            field_def: op.field_def.clone(),
425            field_name,
426            position: op.position.clone(),
427            modified: false,
428        };
429
430        visitor.visit_file_mut(&mut self.syntax_tree);
431
432        if visitor.modified {
433            // Reformat the entire file for struct literals
434            self.content = prettyplease::unparse(&self.syntax_tree);
435            Ok(ModificationResult {
436                changed: true,
437                modified_nodes: backup_nodes,
438            })
439        } else {
440            Ok(ModificationResult {
441                changed: false,
442                modified_nodes: vec![],
443            })
444        }
445    }
446
447    /// Collect backups of all struct literal expressions for a given struct name
448    fn collect_struct_literal_backups(&self, struct_name: &str) -> Vec<BackupNode> {
449        use syn::visit::Visit;
450
451        struct LiteralCollector {
452            struct_name: String,
453            backups: Vec<BackupNode>,
454            counter: usize,
455        }
456
457        impl<'ast> Visit<'ast> for LiteralCollector {
458            fn visit_expr(&mut self, node: &'ast Expr) {
459                if let Expr::Struct(expr_struct) = node {
460                    if let Some(last_seg) = expr_struct.path.segments.last() {
461                        if last_seg.ident.to_string() == self.struct_name {
462                            self.backups.push(BackupNode {
463                                node_type: "ExprStruct".to_string(),
464                                identifier: format!("{}#{}", self.struct_name, self.counter),
465                                original_content: expr_struct.to_token_stream().to_string(),
466                                location: NodeLocation {
467                                    line: 0, // We don't have precise location info in visitor
468                                    column: 0,
469                                    end_line: 0,
470                                    end_column: 0,
471                                },
472                            });
473                            self.counter += 1;
474                        }
475                    }
476                }
477                syn::visit::visit_expr(self, node);
478            }
479        }
480
481        let mut collector = LiteralCollector {
482            struct_name: struct_name.to_string(),
483            backups: Vec::new(),
484            counter: 0,
485        };
486
487        collector.visit_file(&self.syntax_tree);
488        collector.backups
489    }
490
491    pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
492        // Find the enum and clone it to avoid borrowing issues
493        let item_enum = self.syntax_tree.items.iter()
494            .find_map(|item| {
495                if let Item::Enum(e) = item {
496                    if e.ident == op.enum_name {
497                        return Some(e.clone());
498                    }
499                }
500                None
501            })
502            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
503
504        // Check if the enum matches the where filter (if specified)
505        if let Some(ref where_filter) = op.where_filter {
506            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
507                // Enum doesn't match filter - skip without error
508                return Ok(ModificationResult {
509                    changed: false,
510                    modified_nodes: vec![],
511                });
512            }
513        }
514
515        // Create backup of original enum before modification
516        let backup_node = BackupNode {
517            node_type: "ItemEnum".to_string(),
518            identifier: op.enum_name.clone(),
519            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
520            location: self.span_to_location(item_enum.span()),
521        };
522
523        let modified = self.insert_enum_variant(&item_enum, op)?;
524
525        Ok(ModificationResult {
526            changed: modified,
527            modified_nodes: if modified { vec![backup_node] } else { vec![] },
528        })
529    }
530    
531    fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
532        // Parse the new variant
533        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
534        let dummy: ItemEnum = parse_str(&variant_code)
535            .context("Failed to parse variant definition")?;
536
537        let new_variant = dummy.variants.first()
538            .context("No variant found in definition")?
539            .clone();
540
541        // Check if variant already exists
542        let variant_name = new_variant.ident.to_string();
543        if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
544            // Variant already exists, skip adding
545            return Ok(false);
546        }
547
548        // Determine insertion point
549        let insert_pos = match &op.position {
550            InsertPosition::First => {
551                if let Some(first_var) = item_enum.variants.first() {
552                    self.span_to_byte_offset(first_var.span().start())
553                } else {
554                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
555                    brace_pos + 1
556                }
557            }
558            InsertPosition::Last => {
559                if let Some(last_var) = item_enum.variants.last() {
560                    let end = self.span_to_byte_offset(last_var.span().end());
561                    self.find_after_field_end(end)
562                } else {
563                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
564                    brace_pos + 1
565                }
566            }
567            InsertPosition::After(name) => {
568                let variant = item_enum.variants.iter()
569                    .find(|v| v.ident.to_string() == *name)
570                    .with_context(|| format!("Variant '{}' not found", name))?;
571                let end = self.span_to_byte_offset(variant.span().end());
572                self.find_after_field_end(end)
573            }
574            InsertPosition::Before(name) => {
575                let variant = item_enum.variants.iter()
576                    .find(|v| v.ident.to_string() == *name)
577                    .with_context(|| format!("Variant '{}' not found", name))?;
578                self.span_to_byte_offset(variant.span().start())
579            }
580        };
581        
582        let indent = self.get_indentation(insert_pos);
583        let variant_str = new_variant.to_token_stream().to_string();
584        let insert_text = format!("\n{}{},", indent, variant_str);
585        
586        self.content.insert_str(insert_pos, &insert_text);
587        Ok(true)
588    }
589
590    fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
591        // Find the enum and clone it
592        let item_enum = self.syntax_tree.items.iter()
593            .find_map(|item| {
594                if let Item::Enum(e) = item {
595                    if e.ident == op.enum_name {
596                        return Some(e.clone());
597                    }
598                }
599                None
600            })
601            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
602
603        // Check if the enum matches the where filter (if specified)
604        if let Some(ref where_filter) = op.where_filter {
605            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
606                // Enum doesn't match filter - skip without error
607                return Ok(ModificationResult {
608                    changed: false,
609                    modified_nodes: vec![],
610                });
611            }
612        }
613
614        // Create backup of original enum before modification
615        let backup_node = BackupNode {
616            node_type: "ItemEnum".to_string(),
617            identifier: op.enum_name.clone(),
618            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
619            location: self.span_to_location(item_enum.span()),
620        };
621
622        // Parse the new variant to get its name
623        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
624        let dummy: ItemEnum = parse_str(&variant_code)
625            .context("Failed to parse variant definition")?;
626
627        let new_variant = dummy.variants.first()
628            .context("No variant found in definition")?
629            .clone();
630
631        let variant_name = new_variant.ident.to_string();
632
633        // Find the existing variant
634        let existing_variant = item_enum.variants.iter()
635            .find(|v| v.ident.to_string() == variant_name)
636            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
637
638        // Get the span
639        let start = self.span_to_byte_offset(existing_variant.span().start());
640        let end = self.span_to_byte_offset(existing_variant.span().end());
641
642        // Format and replace
643        let variant_str = new_variant.to_token_stream().to_string();
644        self.content.replace_range(start..end, &variant_str);
645
646        Ok(ModificationResult {
647            changed: true,
648            modified_nodes: vec![backup_node],
649        })
650    }
651
652    pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
653        // Find the enum
654        let item_enum = self.syntax_tree.items.iter()
655            .find_map(|item| {
656                if let Item::Enum(e) = item {
657                    if e.ident == op.enum_name {
658                        return Some(e.clone());
659                    }
660                }
661                None
662            })
663            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
664
665        // Check if the enum matches the where filter (if specified)
666        if let Some(ref where_filter) = op.where_filter {
667            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
668                // Enum doesn't match filter - skip without error
669                return Ok(ModificationResult {
670                    changed: false,
671                    modified_nodes: vec![],
672                });
673            }
674        }
675
676        // Create backup of original enum before modification
677        let backup_node = BackupNode {
678            node_type: "ItemEnum".to_string(),
679            identifier: op.enum_name.clone(),
680            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
681            location: self.span_to_location(item_enum.span()),
682        };
683
684        // Find the variant to remove
685        let variant_to_remove = item_enum.variants.iter()
686            .find(|v| v.ident.to_string() == op.variant_name)
687            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
688
689        // Get the span including comma
690        let start = self.span_to_byte_offset(variant_to_remove.span().start());
691        let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
692
693        // Find and include the comma and trailing whitespace
694        while end < self.content.len() {
695            match self.content.as_bytes()[end] as char {
696                ',' => {
697                    end += 1;
698                    if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
699                        end += 1;
700                    }
701                    break;
702                }
703                ' ' | '\t' => end += 1,
704                '\n' => {
705                    end += 1;
706                    break;
707                }
708                _ => break,
709            }
710        }
711
712        // Remove leading whitespace on the line
713        let mut line_start = start;
714        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
715            line_start -= 1;
716        }
717
718        let before_variant = &self.content[line_start..start];
719        if before_variant.trim().is_empty() {
720            self.content.replace_range(line_start..end, "");
721        } else {
722            self.content.replace_range(start..end, "");
723        }
724
725        Ok(ModificationResult {
726            changed: true,
727            modified_nodes: vec![backup_node],
728        })
729    }
730
731    pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
732        if op.auto_detect {
733            // Auto-detect mode: find all missing enum variants
734            self.add_missing_match_arms(op)
735        } else {
736            // Normal mode: add a single match arm
737            self.add_single_match_arm(op)
738        }
739    }
740
741    fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
742        // Parse the pattern and body by creating a dummy match expression
743        let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
744        let expr: syn::Expr = parse_str(&dummy_match)
745            .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
746
747        // Extract the arm from the dummy match
748        let arm = if let syn::Expr::Match(match_expr) = expr {
749            match_expr.arms.into_iter().next()
750                .context("Failed to extract arm from dummy match")?
751        } else {
752            anyhow::bail!("Expected match expression");
753        };
754
755        // Collect backup of function before modification
756        let backup_node = if let Some(ref fn_name) = op.function_name {
757            self.get_function_backup(fn_name)?
758        } else {
759            // If no function specified, we'll backup all modified functions later
760            // For now, create a generic backup
761            BackupNode {
762                node_type: "Unknown".to_string(),
763                identifier: "match_expression".to_string(),
764                original_content: String::new(),
765                location: NodeLocation {
766                    line: 0,
767                    column: 0,
768                    end_line: 0,
769                    end_column: 0,
770                },
771            }
772        };
773
774        // Find and modify match expressions
775        let mut visitor = MatchArmAdder {
776            target_function: op.function_name.clone(),
777            arm_to_add: arm,
778            modified: false,
779            current_function: None,
780            modified_function: None,
781        };
782
783        visitor.visit_file_mut(&mut self.syntax_tree);
784
785        if visitor.modified {
786            // Replace just the modified function
787            self.replace_modified_functions(&visitor.modified_function)?;
788            Ok(ModificationResult {
789                changed: true,
790                modified_nodes: vec![backup_node],
791            })
792        } else {
793            Ok(ModificationResult {
794                changed: false,
795                modified_nodes: vec![],
796            })
797        }
798    }
799
800    /// Format a single item to string using prettyplease
801    fn unparse_item(&self, item: &Item) -> String {
802        let temp_file = syn::File {
803            shebang: None,
804            attrs: Vec::new(),
805            items: vec![item.clone()],
806        };
807        prettyplease::unparse(&temp_file).trim().to_string()
808    }
809
810    /// Get backup of a function before modification
811    fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
812        for item in &self.syntax_tree.items {
813            if let Item::Fn(f) = item {
814                if f.sig.ident == fn_name {
815                    return Ok(BackupNode {
816                        node_type: "ItemFn".to_string(),
817                        identifier: fn_name.to_string(),
818                        original_content: self.unparse_item(&Item::Fn(f.clone())),
819                        location: self.span_to_location(f.span()),
820                    });
821                }
822            }
823        }
824        anyhow::bail!("Function '{}' not found", fn_name)
825    }
826
827    fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
828        // Get the enum name
829        let enum_name = op.enum_name.as_ref()
830            .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
831
832        // Find all enum variants
833        let enum_variants = self.find_enum_variants(enum_name)?;
834
835        if enum_variants.is_empty() {
836            anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
837        }
838
839        // Find existing match arms
840        let existing_patterns = self.find_existing_match_patterns(&op.function_name);
841
842        // Determine missing variants
843        let mut missing_variants = Vec::new();
844        for variant in &enum_variants {
845            let pattern = format!("{}::{}", enum_name, variant);
846            let pattern_normalized = pattern.replace(" ", "");
847
848            let exists = existing_patterns.iter().any(|p| {
849                p.replace(" ", "") == pattern_normalized
850            });
851
852            if !exists {
853                missing_variants.push(variant.clone());
854            }
855        }
856
857        if missing_variants.is_empty() {
858            println!("All enum variants already covered in match expressions");
859            return Ok(ModificationResult {
860                changed: false,
861                modified_nodes: vec![],
862            });
863        }
864
865        // Get backup of function before modification
866        let backup_node = if let Some(ref fn_name) = op.function_name {
867            self.get_function_backup(fn_name)?
868        } else {
869            BackupNode {
870                node_type: "Unknown".to_string(),
871                identifier: "match_expression".to_string(),
872                original_content: String::new(),
873                location: NodeLocation {
874                    line: 0,
875                    column: 0,
876                    end_line: 0,
877                    end_column: 0,
878                },
879            }
880        };
881
882        // Add ALL missing match arms in one pass using a visitor
883        let mut arms_to_add = Vec::new();
884        for variant in &missing_variants {
885            let pattern = format!("{}::{}", enum_name, variant);
886            let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
887            let expr: syn::Expr = parse_str(&dummy_match)
888                .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
889
890            if let syn::Expr::Match(match_expr) = expr {
891                if let Some(arm) = match_expr.arms.into_iter().next() {
892                    arms_to_add.push((pattern.clone(), arm));
893                }
894            }
895        }
896
897        // Find and modify match expressions with all arms at once
898        let mut visitor = MultiMatchArmAdder {
899            target_function: op.function_name.clone(),
900            arms_to_add,
901            modified: false,
902            current_function: None,
903            modified_function: None,
904        };
905
906        visitor.visit_file_mut(&mut self.syntax_tree);
907
908        if visitor.modified {
909            // Print what was added
910            for variant in &missing_variants {
911                println!("Added match arm for: {}::{}", enum_name, variant);
912            }
913
914            // Replace just the modified function
915            self.replace_modified_functions(&visitor.modified_function)?;
916            Ok(ModificationResult {
917                changed: true,
918                modified_nodes: vec![backup_node],
919            })
920        } else {
921            Ok(ModificationResult {
922                changed: false,
923                modified_nodes: vec![],
924            })
925        }
926    }
927
928    fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
929        // Find the enum in the syntax tree
930        for item in &self.syntax_tree.items {
931            if let Item::Enum(e) = item {
932                if e.ident == enum_name {
933                    let variants: Vec<String> = e.variants.iter()
934                        .map(|v| v.ident.to_string())
935                        .collect();
936                    return Ok(variants);
937                }
938            }
939        }
940
941        Ok(Vec::new())
942    }
943
944    fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
945        use syn::visit::Visit;
946
947        struct PatternCollector {
948            target_function: Option<String>,
949            current_function: Option<String>,
950            patterns: Vec<String>,
951        }
952
953        impl<'ast> Visit<'ast> for PatternCollector {
954            fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
955                let prev_fn = self.current_function.clone();
956                self.current_function = Some(node.sig.ident.to_string());
957                syn::visit::visit_item_fn(self, node);
958                self.current_function = prev_fn;
959            }
960
961            fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
962                // Check if we're in the right function (if specified)
963                if let Some(ref target) = self.target_function {
964                    if self.current_function.as_ref() != Some(target) {
965                        syn::visit::visit_expr_match(self, node);
966                        return;
967                    }
968                }
969
970                // Collect all patterns
971                for arm in &node.arms {
972                    self.patterns.push(arm.pat.to_token_stream().to_string());
973                }
974
975                syn::visit::visit_expr_match(self, node);
976            }
977        }
978
979        let mut collector = PatternCollector {
980            target_function: function_name.clone(),
981            current_function: None,
982            patterns: Vec::new(),
983        };
984
985        collector.visit_file(&self.syntax_tree);
986        collector.patterns
987    }
988
989    pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
990        // Get backup of function before modification
991        let backup_node = if let Some(ref fn_name) = op.function_name {
992            self.get_function_backup(fn_name)?
993        } else {
994            BackupNode {
995                node_type: "Unknown".to_string(),
996                identifier: "match_expression".to_string(),
997                original_content: String::new(),
998                location: NodeLocation {
999                    line: 0,
1000                    column: 0,
1001                    end_line: 0,
1002                    end_column: 0,
1003                },
1004            }
1005        };
1006
1007        // Parse the new body
1008        let new_body: syn::Expr = parse_str(&op.new_body)
1009            .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1010
1011        // Find and modify match expressions
1012        let mut visitor = MatchArmUpdater {
1013            target_function: op.function_name.clone(),
1014            pattern_to_match: op.pattern.clone(),
1015            new_body,
1016            modified: false,
1017            current_function: None,
1018            modified_function: None,
1019        };
1020
1021        visitor.visit_file_mut(&mut self.syntax_tree);
1022
1023        if visitor.modified {
1024            // Replace just the modified function
1025            self.replace_modified_functions(&visitor.modified_function)?;
1026            Ok(ModificationResult {
1027                changed: true,
1028                modified_nodes: vec![backup_node],
1029            })
1030        } else {
1031            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1032        }
1033    }
1034
1035    pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1036        // Get backup of function before modification
1037        let backup_node = if let Some(ref fn_name) = op.function_name {
1038            self.get_function_backup(fn_name)?
1039        } else {
1040            BackupNode {
1041                node_type: "Unknown".to_string(),
1042                identifier: "match_expression".to_string(),
1043                original_content: String::new(),
1044                location: NodeLocation {
1045                    line: 0,
1046                    column: 0,
1047                    end_line: 0,
1048                    end_column: 0,
1049                },
1050            }
1051        };
1052
1053        // Find and modify match expressions
1054        let mut visitor = MatchArmRemover {
1055            target_function: op.function_name.clone(),
1056            pattern_to_remove: op.pattern.clone(),
1057            modified: false,
1058            current_function: None,
1059            modified_function: None,
1060        };
1061
1062        visitor.visit_file_mut(&mut self.syntax_tree);
1063
1064        if visitor.modified {
1065            // Replace just the modified function
1066            self.replace_modified_functions(&visitor.modified_function)?;
1067            Ok(ModificationResult {
1068                changed: true,
1069                modified_nodes: vec![backup_node],
1070            })
1071        } else {
1072            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1073        }
1074    }
1075
1076    pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1077        // Parse the method definition
1078        let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1079        let dummy: syn::ItemImpl = parse_str(&method_code)
1080            .context("Failed to parse method definition")?;
1081
1082        let new_method = dummy.items.first()
1083            .context("No method found in definition")?
1084            .clone();
1085
1086        // Get the method name for idempotency check
1087        let method_name = match &new_method {
1088            syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1089            _ => anyhow::bail!("Only method definitions are supported"),
1090        };
1091
1092        // Find the impl block
1093        let impl_index = self.syntax_tree.items.iter().position(|item| {
1094            if let Item::Impl(impl_block) = item {
1095                // Check if this is the right impl block
1096                if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1097                    if let Some(segment) = type_path.path.segments.last() {
1098                        return segment.ident == op.target;
1099                    }
1100                }
1101            }
1102            false
1103        }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1104
1105        // Check if method already exists (idempotent)
1106        let impl_block = match &self.syntax_tree.items[impl_index] {
1107            Item::Impl(i) => i,
1108            _ => unreachable!(),
1109        };
1110
1111        let method_exists = impl_block.items.iter().any(|item| {
1112            if let syn::ImplItem::Fn(f) = item {
1113                f.sig.ident == method_name
1114            } else {
1115                false
1116            }
1117        });
1118
1119        if method_exists {
1120            return Ok(ModificationResult {
1121                changed: false,
1122                modified_nodes: vec![],
1123            });
1124        }
1125
1126        // Create backup of original impl block before modification
1127        let backup_node = BackupNode {
1128            node_type: "ItemImpl".to_string(),
1129            identifier: op.target.clone(),
1130            original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1131            location: self.span_to_location(impl_block.span()),
1132        };
1133
1134        // Get the span before modification
1135        let impl_span = impl_block.span();
1136
1137        // Add the method to the impl block
1138        match &mut self.syntax_tree.items[impl_index] {
1139            Item::Impl(impl_block) => {
1140                // Add based on position
1141                match &op.position {
1142                    InsertPosition::First => {
1143                        impl_block.items.insert(0, new_method);
1144                    }
1145                    InsertPosition::Last => {
1146                        impl_block.items.push(new_method);
1147                    }
1148                    InsertPosition::After(name) => {
1149                        let pos = impl_block.items.iter().position(|item| {
1150                            if let syn::ImplItem::Fn(f) = item {
1151                                f.sig.ident == name
1152                            } else {
1153                                false
1154                            }
1155                        }).with_context(|| format!("Method '{}' not found", name))?;
1156                        impl_block.items.insert(pos + 1, new_method);
1157                    }
1158                    InsertPosition::Before(name) => {
1159                        let pos = impl_block.items.iter().position(|item| {
1160                            if let syn::ImplItem::Fn(f) = item {
1161                                f.sig.ident == name
1162                            } else {
1163                                false
1164                            }
1165                        }).with_context(|| format!("Method '{}' not found", name))?;
1166                        impl_block.items.insert(pos, new_method);
1167                    }
1168                }
1169            }
1170            _ => unreachable!(),
1171        }
1172
1173        // Use prettyplease to format just this impl block
1174        self.replace_formatted_item(impl_index, impl_span)?;
1175
1176        Ok(ModificationResult {
1177            changed: true,
1178            modified_nodes: vec![backup_node],
1179        })
1180    }
1181
1182    pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1183        // Parse the use statement
1184        let use_code = format!("use {};", op.use_path);
1185        let use_item: syn::ItemUse = parse_str(&use_code)
1186            .context("Failed to parse use statement")?;
1187
1188        // Check if this use statement already exists (idempotent)
1189        let use_exists = self.syntax_tree.items.iter().any(|item| {
1190            if let Item::Use(existing_use) = item {
1191                // Compare the use trees
1192                existing_use.tree.to_token_stream().to_string() ==
1193                    use_item.tree.to_token_stream().to_string()
1194            } else {
1195                false
1196            }
1197        });
1198
1199        if use_exists {
1200            return Ok(ModificationResult {
1201                changed: false,
1202                modified_nodes: vec![],
1203            });
1204        }
1205
1206        // Create a simple backup for use statements (track by line position)
1207        let backup_node = BackupNode {
1208            node_type: "ItemUse".to_string(),
1209            identifier: op.use_path.clone(),
1210            original_content: format!("use {};", op.use_path),
1211            location: NodeLocation {
1212                line: 0,
1213                column: 0,
1214                end_line: 0,
1215                end_column: 0,
1216            },
1217        };
1218
1219        // Find the position to insert the use statement
1220        let insert_index = match &op.position {
1221            InsertPosition::First => 0,
1222            InsertPosition::Last => {
1223                // Find the last use statement
1224                self.syntax_tree.items.iter()
1225                    .rposition(|item| matches!(item, Item::Use(_)))
1226                    .map(|i| i + 1)
1227                    .unwrap_or(0)
1228            }
1229            InsertPosition::After(path) => {
1230                // Find the use statement matching the path
1231                let pos = self.syntax_tree.items.iter().position(|item| {
1232                    if let Item::Use(u) = item {
1233                        u.tree.to_token_stream().to_string().contains(path)
1234                    } else {
1235                        false
1236                    }
1237                }).with_context(|| format!("Use statement for '{}' not found", path))?;
1238                pos + 1
1239            }
1240            InsertPosition::Before(path) => {
1241                // Find the use statement matching the path
1242                self.syntax_tree.items.iter().position(|item| {
1243                    if let Item::Use(u) = item {
1244                        u.tree.to_token_stream().to_string().contains(path)
1245                    } else {
1246                        false
1247                    }
1248                }).with_context(|| format!("Use statement for '{}' not found", path))?
1249            }
1250        };
1251
1252        // Insert the use statement into the AST
1253        self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1254
1255        // Find the byte position in the source where we need to insert
1256        // We want to insert at the beginning of a line
1257        let insert_line_pos = if insert_index == 0 {
1258            // Insert at very beginning
1259            0
1260        } else {
1261            // Insert after the previous item
1262            let prev_item = &self.syntax_tree.items[insert_index - 1];
1263            let span = prev_item.span();
1264            let end_pos = self.span_to_byte_offset(span.end());
1265
1266            // Find the end of this line (where the newline is)
1267            let mut line_end = end_pos;
1268            while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1269                line_end += 1;
1270            }
1271            // Move past the newline to the start of the next line
1272            if line_end < self.content.len() {
1273                line_end + 1
1274            } else {
1275                // At end of file, add a newline first
1276                self.content.push('\n');
1277                self.content.len()
1278            }
1279        };
1280
1281        // Format the use statement
1282        let use_str = format!("use {};\n", op.use_path);
1283
1284        // Insert the use statement
1285        self.content.insert_str(insert_line_pos, &use_str);
1286
1287        Ok(ModificationResult {
1288            changed: true,
1289            modified_nodes: vec![backup_node],
1290        })
1291    }
1292
1293    pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1294        // Find the target item (struct or enum)
1295        let item_index = self.syntax_tree.items.iter().position(|item| {
1296            match (&op.target_type as &str, item) {
1297                ("struct", Item::Struct(s)) => s.ident == op.target_name,
1298                ("enum", Item::Enum(e)) => e.ident == op.target_name,
1299                _ => false,
1300            }
1301        }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1302
1303        // Get the item and check for existing derives
1304        let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1305            Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1306            Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1307            _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1308        };
1309
1310        // Check if the item matches the where filter (if specified)
1311        if let Some(ref where_filter) = op.where_filter {
1312            if !self.matches_where_filter(item_attrs, where_filter)? {
1313                // Item doesn't match filter - skip without error
1314                return Ok(ModificationResult {
1315                    changed: false,
1316                    modified_nodes: vec![],
1317                });
1318            }
1319        }
1320
1321        // Create backup of original item before modification
1322        let backup_node = BackupNode {
1323            node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1324            identifier: op.target_name.clone(),
1325            original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1326            location: self.span_to_location(item_span),
1327        };
1328
1329        // Filter out derives that already exist (idempotent)
1330        let new_derives: Vec<String> = op.derives.iter()
1331            .filter(|d| !existing_derives.contains(&d.to_string()))
1332            .cloned()
1333            .collect();
1334
1335        if new_derives.is_empty() {
1336            // All derives already exist
1337            return Ok(ModificationResult {
1338                changed: false,
1339                modified_nodes: vec![],
1340            });
1341        }
1342
1343        // Combine existing and new derives
1344        let mut all_derives = existing_derives;
1345        all_derives.extend(new_derives);
1346
1347        // Convert to string refs for the update function
1348        let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1349
1350        // Update the AST item's attributes
1351        match &mut self.syntax_tree.items[item_index] {
1352            Item::Struct(s) => {
1353                Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1354            }
1355            Item::Enum(e) => {
1356                Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1357            }
1358            _ => unreachable!(),
1359        }
1360
1361        // Use prettyplease to format just this item
1362        self.replace_formatted_item(item_index, item_span)?;
1363
1364        Ok(ModificationResult {
1365            changed: true,
1366            modified_nodes: vec![backup_node],
1367        })
1368    }
1369
1370    /// Replace an item in the content with a formatted version
1371    fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1372        // Get the item start and end positions from the original source
1373        let item_start_pos = self.span_to_byte_offset(original_span.start());
1374        let item_end_pos = self.span_to_byte_offset(original_span.end());
1375
1376        // Find the actual start (including attributes)
1377        let mut actual_start = item_start_pos;
1378
1379        // Search backwards for attributes
1380        let mut temp_pos = item_start_pos;
1381        while temp_pos > 0 {
1382            // Move to previous line
1383            temp_pos = temp_pos.saturating_sub(1);
1384            let mut line_start = temp_pos;
1385            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1386                line_start -= 1;
1387            }
1388
1389            let line = if temp_pos < self.content.len() {
1390                &self.content[line_start..temp_pos + 1]
1391            } else {
1392                &self.content[line_start..]
1393            };
1394            let trimmed = line.trim();
1395
1396            if trimmed.starts_with("#[") {
1397                actual_start = line_start;
1398                temp_pos = line_start;
1399            } else if trimmed.is_empty() {
1400                temp_pos = line_start;
1401            } else {
1402                break;
1403            }
1404
1405            if line_start == 0 {
1406                break;
1407            }
1408        }
1409
1410        // Create a temporary file with just this item for pretty formatting
1411        let item_clone = self.syntax_tree.items[item_index].clone();
1412        let temp_file = syn::File {
1413            shebang: None,
1414            attrs: Vec::new(),
1415            items: vec![item_clone],
1416        };
1417
1418        // Format the item using prettyplease
1419        let formatted = prettyplease::unparse(&temp_file);
1420        let formatted = formatted.trim();
1421
1422        // Replace in content
1423        self.content.replace_range(actual_start..item_end_pos, formatted);
1424
1425        Ok(())
1426    }
1427
1428    /// Extract existing derive traits from attributes - returns owned Strings
1429    fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1430        for attr in attrs {
1431            if attr.path().is_ident("derive") {
1432                if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1433                    let tokens_str = meta_list.tokens.to_string();
1434                    return tokens_str
1435                        .split(',')
1436                        .map(|s| s.trim().to_string())
1437                        .collect();
1438                }
1439            }
1440        }
1441        Vec::new()
1442    }
1443
1444    /// Check if an item matches the where filter criteria
1445    /// Supports filters like:
1446    /// - "derives_trait:Clone" - matches if item derives Clone
1447    /// - "derives_trait:Clone,Debug" - matches if item derives Clone OR Debug
1448    fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1449        // Parse the filter: "derives_trait:Clone,Debug"
1450        if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1451            let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1452            let existing_derives = Self::extract_derives(attrs);
1453
1454            // Check if ANY of the required traits are present
1455            for required_trait in required_traits {
1456                if existing_derives.iter().any(|d| d == required_trait) {
1457                    return Ok(true);
1458                }
1459            }
1460            return Ok(false);
1461        }
1462
1463        // Unknown filter type - default to match (don't break existing behavior)
1464        Ok(true)
1465    }
1466
1467    /// Update or create derive attribute in the attribute list
1468    fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1469        let derive_str = derives.join(", ");
1470
1471        // Parse a dummy struct with the derive to extract the attribute
1472        let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1473        let parsed: syn::ItemStruct = parse_str(&dummy)
1474            .context("Failed to parse derive attribute")?;
1475
1476        let new_attr = parsed.attrs.into_iter()
1477            .find(|a| a.path().is_ident("derive"))
1478            .context("Failed to extract derive attribute")?;
1479
1480        // Find existing derive attribute and replace it
1481        if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1482            attrs[pos] = new_attr;
1483        } else {
1484            // Add new derive attribute at the beginning
1485            attrs.insert(0, new_attr);
1486        }
1487
1488        Ok(())
1489    }
1490
1491    /// Replace the modified function(s) in the content with formatted versions
1492    fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1493        // If no specific function was targeted, format the entire file
1494        if modified_function.is_none() {
1495            self.content = prettyplease::unparse(&self.syntax_tree);
1496            return Ok(());
1497        }
1498
1499        // Parse the ORIGINAL content to get the correct spans
1500        let original_syntax_tree: File = syn::parse_str(&self.content)
1501            .context("Failed to re-parse original content")?;
1502
1503        let function_name = modified_function.as_ref().unwrap();
1504
1505        // Find the function in the ORIGINAL syntax tree to get correct byte positions
1506        let original_fn = original_syntax_tree.items.iter()
1507            .find_map(|item| {
1508                if let Item::Fn(f) = item {
1509                    if f.sig.ident == function_name {
1510                        return Some(f.clone());
1511                    }
1512                }
1513                None
1514            })
1515            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1516
1517        // Get the span of the original function (these are the correct byte positions)
1518        let start = self.span_to_byte_offset(original_fn.span().start());
1519        let end = self.span_to_byte_offset(original_fn.span().end());
1520
1521        // Find the MODIFIED function in the modified syntax tree
1522        let modified_fn = self.syntax_tree.items.iter()
1523            .find_map(|item| {
1524                if let Item::Fn(f) = item {
1525                    if f.sig.ident == function_name {
1526                        return Some(f.clone());
1527                    }
1528                }
1529                None
1530            })
1531            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1532
1533        // Format just the modified function using prettyplease
1534        let dummy_file = syn::File {
1535            shebang: None,
1536            attrs: Vec::new(),
1537            items: vec![Item::Fn(modified_fn)],
1538        };
1539
1540        let formatted_fn = prettyplease::unparse(&dummy_file);
1541
1542        // Extract just the function (remove any extra newlines at start/end)
1543        let formatted_fn = formatted_fn.trim();
1544
1545        // Replace the function in the original content using original spans
1546        self.content.replace_range(start..end, formatted_fn);
1547
1548        Ok(())
1549    }
1550    
1551    fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1552        let line_idx = pos.line.saturating_sub(1);
1553        if line_idx < self.line_offsets.len() {
1554            self.line_offsets[line_idx] + pos.column
1555        } else {
1556            self.content.len()
1557        }
1558    }
1559    
1560    fn find_after_field_end(&self, pos: usize) -> usize {
1561        // Look for comma or newline after the field
1562        let mut i = pos;
1563        while i < self.content.len() {
1564            match self.content.as_bytes()[i] as char {
1565                ',' => return i + 1,
1566                '\n' => return i + 1,
1567                _ => i += 1,
1568            }
1569        }
1570        pos
1571    }
1572    
1573    fn get_indentation(&self, pos: usize) -> String {
1574        // Find the start of the current line
1575        let mut line_start = pos;
1576        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1577            line_start -= 1;
1578        }
1579        
1580        // Count spaces/tabs at the start of the line
1581        let mut indent = String::new();
1582        let mut i = line_start;
1583        while i < self.content.len() {
1584            match self.content.as_bytes()[i] as char {
1585                ' ' | '\t' => {
1586                    indent.push(self.content.as_bytes()[i] as char);
1587                    i += 1;
1588                }
1589                _ => break,
1590            }
1591        }
1592        
1593        // If we're inserting in an empty struct/enum, add default indentation
1594        if indent.is_empty() {
1595            "    ".to_string()
1596        } else {
1597            indent
1598        }
1599    }
1600    
1601    pub fn to_string(&self) -> String {
1602        self.content.clone()
1603    }
1604
1605    /// Inspect and list AST nodes (e.g., struct literals) in the file
1606    pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1607        use syn::visit::Visit;
1608        use crate::operations::InspectResult;
1609
1610        let mut results = Vec::new();
1611
1612        match node_type {
1613            "struct-literal" => {
1614                // Find all struct literal expressions
1615                struct StructLiteralVisitor<'a> {
1616                    results: &'a mut Vec<InspectResult>,
1617                    name_filter: Option<&'a str>,
1618                    editor: &'a RustEditor,
1619                }
1620
1621                impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1622                    fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1623                        // Extract the struct name from the path
1624                        let struct_name = if let Some(ident) = node.path.get_ident() {
1625                            ident.to_string()
1626                        } else {
1627                            // Handle paths like "module::StructName"
1628                            node.path.segments.last()
1629                                .map(|seg| seg.ident.to_string())
1630                                .unwrap_or_default()
1631                        };
1632
1633                        // Apply name filter if specified
1634                        if let Some(filter) = self.name_filter {
1635                            if struct_name != filter {
1636                                syn::visit::visit_expr_struct(self, node);
1637                                return;
1638                            }
1639                        }
1640
1641                        // Format the struct literal
1642                        let snippet = self.editor.format_expr_struct(node);
1643                        let location = self.editor.span_to_location(node.span());
1644
1645                        self.results.push(InspectResult {
1646                            file_path: String::new(), // Will be filled in by caller
1647                            node_type: "ExprStruct".to_string(),
1648                            identifier: struct_name,
1649                            location,
1650                            snippet,
1651                        });
1652
1653                        // Continue visiting nested expressions
1654                        syn::visit::visit_expr_struct(self, node);
1655                    }
1656                }
1657
1658                let mut visitor = StructLiteralVisitor {
1659                    results: &mut results,
1660                    name_filter,
1661                    editor: self,
1662                };
1663
1664                // Visit all items in the file
1665                for item in &self.syntax_tree.items {
1666                    syn::visit::visit_item(&mut visitor, item);
1667                }
1668            }
1669            _ => anyhow::bail!("Unsupported node type: {}", node_type),
1670        }
1671
1672        Ok(results)
1673    }
1674
1675    /// Format an ExprStruct node as a string - extracts original source
1676    fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
1677        // Extract the original source code from the file content using the span
1678        let start = self.span_to_byte_offset(expr.span().start());
1679        let end = self.span_to_byte_offset(expr.span().end());
1680
1681        // Get the original text and collapse to single line
1682        let original = &self.content[start..end];
1683
1684        // Replace multiple whitespace/newlines with single space for single-line format
1685        original.split_whitespace().collect::<Vec<_>>().join(" ")
1686    }
1687
1688    /// Find the index of an item by type and name
1689    #[allow(dead_code)]
1690    pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
1691        for (index, item) in self.syntax_tree.items.iter().enumerate() {
1692            match (node_type, item) {
1693                ("struct", Item::Struct(s)) if s.ident == name => {
1694                    return Ok(index);
1695                }
1696                ("enum", Item::Enum(e)) if e.ident == name => {
1697                    return Ok(index);
1698                }
1699                ("fn", Item::Fn(f)) if f.sig.ident == name => {
1700                    return Ok(index);
1701                }
1702                ("impl", Item::Impl(impl_block)) => {
1703                    // For impl blocks, match on the self_ty
1704                    if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1705                        if let Some(segment) = type_path.path.segments.last() {
1706                            if segment.ident == name {
1707                                return Ok(index);
1708                            }
1709                        }
1710                    }
1711                }
1712                _ => {}
1713            }
1714        }
1715
1716        anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
1717    }
1718
1719    /// Replace an item at a specific index with a new item
1720    #[allow(dead_code)]
1721    pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
1722        if index >= self.syntax_tree.items.len() {
1723            anyhow::bail!("Index {} out of bounds", index);
1724        }
1725
1726        // Replace the item in the syntax tree
1727        self.syntax_tree.items[index] = new_item;
1728
1729        // Reformat the entire file using prettyplease
1730        self.content = prettyplease::unparse(&self.syntax_tree);
1731
1732        // Recompute line offsets
1733        self.line_offsets = Self::compute_line_offsets(&self.content);
1734
1735        Ok(())
1736    }
1737
1738    pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
1739        let mut locations = Vec::new();
1740        
1741        for item in &self.syntax_tree.items {
1742            match (node_type, item) {
1743                ("struct", Item::Struct(s)) if s.ident == name => {
1744                    locations.push(self.span_to_location(s.span()));
1745                }
1746                ("enum", Item::Enum(e)) if e.ident == name => {
1747                    locations.push(self.span_to_location(e.span()));
1748                }
1749                ("fn", Item::Fn(f)) if f.sig.ident == name => {
1750                    locations.push(self.span_to_location(f.span()));
1751                }
1752                _ => {}
1753            }
1754        }
1755        
1756        if locations.is_empty() {
1757            anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
1758        }
1759        
1760        Ok(locations)
1761    }
1762    
1763    fn span_to_location(&self, span: Span) -> NodeLocation {
1764        let start = span.start();
1765        let end = span.end();
1766
1767        NodeLocation {
1768            line: start.line,
1769            column: start.column,
1770            end_line: end.line,
1771            end_column: end.column,
1772        }
1773    }
1774}
1775
1776// Visitor for adding match arms
1777struct MatchArmAdder {
1778    target_function: Option<String>,
1779    arm_to_add: Arm,
1780    modified: bool,
1781    current_function: Option<String>,
1782    modified_function: Option<String>,
1783}
1784
1785impl VisitMut for MatchArmAdder {
1786    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
1787        let prev_fn = self.current_function.clone();
1788        self.current_function = Some(node.sig.ident.to_string());
1789
1790        // Continue visiting nested items
1791        syn::visit_mut::visit_item_fn_mut(self, node);
1792
1793        self.current_function = prev_fn;
1794    }
1795
1796    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
1797        // Check if we're in the right function (if specified)
1798        if let Some(ref target) = self.target_function {
1799            if self.current_function.as_ref() != Some(target) {
1800                // Continue visiting nested expressions
1801                syn::visit_mut::visit_expr_match_mut(self, node);
1802                return;
1803            }
1804        }
1805
1806        // Check if the pattern already exists (idempotent)
1807        let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
1808        let already_exists = node.arms.iter().any(|arm| {
1809            arm.pat.to_token_stream().to_string() == pattern_str
1810        });
1811
1812        if !already_exists {
1813            // Add the arm to the end
1814            node.arms.push(self.arm_to_add.clone());
1815            self.modified = true;
1816            self.modified_function = self.current_function.clone();
1817        }
1818
1819        // Continue visiting nested expressions
1820        syn::visit_mut::visit_expr_match_mut(self, node);
1821    }
1822}
1823
1824// Visitor for updating match arms
1825struct MatchArmUpdater {
1826    target_function: Option<String>,
1827    pattern_to_match: String,
1828    new_body: syn::Expr,
1829    modified: bool,
1830    current_function: Option<String>,
1831    modified_function: Option<String>,
1832}
1833
1834impl VisitMut for MatchArmUpdater {
1835    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
1836        let prev_fn = self.current_function.clone();
1837        self.current_function = Some(node.sig.ident.to_string());
1838
1839        syn::visit_mut::visit_item_fn_mut(self, node);
1840
1841        self.current_function = prev_fn;
1842    }
1843
1844    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
1845        // Check if we're in the right function (if specified)
1846        if let Some(ref target) = self.target_function {
1847            if self.current_function.as_ref() != Some(target) {
1848                syn::visit_mut::visit_expr_match_mut(self, node);
1849                return;
1850            }
1851        }
1852
1853        // Find and update the matching arm
1854        for arm in &mut node.arms {
1855            let pattern_str = arm.pat.to_token_stream().to_string();
1856            // Normalize whitespace for comparison
1857            let pattern_normalized = pattern_str.replace(" ", "");
1858            let target_normalized = self.pattern_to_match.replace(" ", "");
1859
1860            if pattern_normalized == target_normalized {
1861                arm.body = Box::new(self.new_body.clone());
1862                self.modified = true;
1863                self.modified_function = self.current_function.clone();
1864                break;
1865            }
1866        }
1867
1868        syn::visit_mut::visit_expr_match_mut(self, node);
1869    }
1870}
1871
1872// Visitor for removing match arms
1873struct MatchArmRemover {
1874    target_function: Option<String>,
1875    pattern_to_remove: String,
1876    modified: bool,
1877    current_function: Option<String>,
1878    modified_function: Option<String>,
1879}
1880
1881impl VisitMut for MatchArmRemover {
1882    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
1883        let prev_fn = self.current_function.clone();
1884        self.current_function = Some(node.sig.ident.to_string());
1885
1886        syn::visit_mut::visit_item_fn_mut(self, node);
1887
1888        self.current_function = prev_fn;
1889    }
1890
1891    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
1892        // Check if we're in the right function (if specified)
1893        if let Some(ref target) = self.target_function {
1894            if self.current_function.as_ref() != Some(target) {
1895                syn::visit_mut::visit_expr_match_mut(self, node);
1896                return;
1897            }
1898        }
1899
1900        // Find and remove the matching arm
1901        let mut index_to_remove = None;
1902        for (i, arm) in node.arms.iter().enumerate() {
1903            let pattern_str = arm.pat.to_token_stream().to_string();
1904            // Normalize whitespace for comparison
1905            let pattern_normalized = pattern_str.replace(" ", "");
1906            let target_normalized = self.pattern_to_remove.replace(" ", "");
1907
1908            if pattern_normalized == target_normalized {
1909                index_to_remove = Some(i);
1910                break;
1911            }
1912        }
1913
1914        if let Some(index) = index_to_remove {
1915            node.arms.remove(index);
1916            self.modified = true;
1917            self.modified_function = self.current_function.clone();
1918        }
1919
1920        syn::visit_mut::visit_expr_match_mut(self, node);
1921    }
1922}
1923
1924// Visitor for adding multiple match arms at once (for auto-detect)
1925struct MultiMatchArmAdder {
1926    target_function: Option<String>,
1927    arms_to_add: Vec<(String, Arm)>,  // (pattern_string, arm)
1928    modified: bool,
1929    current_function: Option<String>,
1930    modified_function: Option<String>,
1931}
1932
1933impl VisitMut for MultiMatchArmAdder {
1934    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
1935        let prev_fn = self.current_function.clone();
1936        self.current_function = Some(node.sig.ident.to_string());
1937
1938        syn::visit_mut::visit_item_fn_mut(self, node);
1939
1940        self.current_function = prev_fn;
1941    }
1942
1943    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
1944        // Check if we're in the right function (if specified)
1945        if let Some(ref target) = self.target_function {
1946            if self.current_function.as_ref() != Some(target) {
1947                syn::visit_mut::visit_expr_match_mut(self, node);
1948                return;
1949            }
1950        }
1951
1952        // Add all missing arms
1953        for (pattern_str, arm) in &self.arms_to_add {
1954            // Check if the pattern already exists (idempotent)
1955            let already_exists = node.arms.iter().any(|existing_arm| {
1956                existing_arm.pat.to_token_stream().to_string() == *pattern_str
1957            });
1958
1959            if !already_exists {
1960                node.arms.push(arm.clone());
1961                self.modified = true;
1962                self.modified_function = self.current_function.clone();
1963            }
1964        }
1965
1966        syn::visit_mut::visit_expr_match_mut(self, node);
1967    }
1968}
1969
1970// Visitor for adding fields to struct literal expressions
1971struct StructLiteralFieldAdder {
1972    struct_name: String,
1973    field_def: String,
1974    field_name: String,
1975    position: InsertPosition,
1976    modified: bool,
1977}
1978
1979impl VisitMut for StructLiteralFieldAdder {
1980    fn visit_expr_mut(&mut self, node: &mut Expr) {
1981        // Check if this is a struct literal expression
1982        if let Expr::Struct(expr_struct) = node {
1983            // Get the struct name (last segment of the path)
1984            let struct_name = expr_struct.path.segments.last()
1985                .map(|seg| seg.ident.to_string());
1986
1987            if struct_name.as_ref() == Some(&self.struct_name) {
1988                // Check if field already exists (idempotent)
1989                let field_exists = expr_struct.fields.iter().any(|fv| {
1990                    fv.member.to_token_stream().to_string() == self.field_name
1991                });
1992
1993                if !field_exists {
1994                    // Parse the field value from field_def
1995                    // field_def is like "return_type: None"
1996                    let field_value_code = format!("{{ {} }}", self.field_def);
1997                    if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
1998                        if let Some(new_fv) = expr.fields.first() {
1999                            // Determine where to insert
2000                            match &self.position {
2001                                InsertPosition::First => {
2002                                    expr_struct.fields.insert(0, new_fv.clone());
2003                                    self.modified = true;
2004                                }
2005                                InsertPosition::Last => {
2006                                    expr_struct.fields.push(new_fv.clone());
2007                                    self.modified = true;
2008                                }
2009                                InsertPosition::After(after_field) => {
2010                                    // Find the position of the field to insert after
2011                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2012                                        fv.member.to_token_stream().to_string() == *after_field
2013                                    }) {
2014                                        expr_struct.fields.insert(pos + 1, new_fv.clone());
2015                                        self.modified = true;
2016                                    }
2017                                }
2018                                InsertPosition::Before(before_field) => {
2019                                    // Find the position of the field to insert before
2020                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2021                                        fv.member.to_token_stream().to_string() == *before_field
2022                                    }) {
2023                                        expr_struct.fields.insert(pos, new_fv.clone());
2024                                        self.modified = true;
2025                                    }
2026                                }
2027                            }
2028                        }
2029                    }
2030                }
2031            }
2032        }
2033
2034        // IMPORTANT: Visit children AFTER processing this node
2035        // This ensures we traverse into nested expressions
2036        syn::visit_mut::visit_expr_mut(self, node);
2037    }
2038}