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            Operation::Transform(op) => self.transform(op),
84        }
85    }
86    
87    pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
88        // Find the struct and clone it to avoid borrowing issues
89        let item_struct = self.syntax_tree.items.iter()
90            .find_map(|item| {
91                if let Item::Struct(s) = item {
92                    if s.ident == op.struct_name {
93                        return Some(s.clone());
94                    }
95                }
96                None
97            })
98            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
99
100        // Check if the struct matches the where filter (if specified)
101        if let Some(ref where_filter) = op.where_filter {
102            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
103                // Struct doesn't match filter - skip without error
104                return Ok(ModificationResult {
105                    changed: false,
106                    modified_nodes: vec![],
107                });
108            }
109        }
110
111        // Create backup of original struct before modification
112        let backup_node = BackupNode {
113            node_type: "ItemStruct".to_string(),
114            identifier: op.struct_name.clone(),
115            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
116            location: self.span_to_location(item_struct.span()),
117        };
118
119        // First, insert the field into the struct definition
120        let modified = self.insert_struct_field(&item_struct, op)
121            .context("Failed to add field to struct definition")?;
122
123        if !modified {
124            return Ok(ModificationResult {
125                changed: false,
126                modified_nodes: vec![],
127            });
128        }
129
130        let mut modified_nodes = vec![backup_node];
131
132        // If literal_default is provided and struct field was added, also update struct literals
133        if let Some(ref literal_default) = op.literal_default {
134            // Re-parse the content to update syntax_tree with the struct field changes
135            self.syntax_tree = syn::parse_str(&self.content)
136                .context("Failed to re-parse content after adding struct field")?;
137            self.line_offsets = Self::compute_line_offsets(&self.content);
138
139            // Extract field name from field_def (e.g., "return_type: Option<Type>" -> "return_type")
140            let field_name = op.field_def.split(':')
141                .next()
142                .map(|s| s.trim().to_string())
143                .context("Failed to extract field name from field definition")?;
144
145            // Create the AddStructLiteralFieldOp
146            let literal_op = AddStructLiteralFieldOp {
147                struct_name: op.struct_name.clone(),
148                field_def: format!("{}: {}", field_name, literal_default),
149                position: op.position.clone(),
150            };
151
152            // Update all struct literals
153            let literal_result = self.add_struct_literal_field(&literal_op)
154                .context("Failed to update struct literals")?;
155            modified_nodes.extend(literal_result.modified_nodes);
156        }
157
158        Ok(ModificationResult {
159            changed: true,
160            modified_nodes,
161        })
162    }
163    
164    fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
165        if let Fields::Named(ref fields) = item_struct.fields {
166            // Parse the new field
167            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
168            let dummy: ItemStruct = parse_str(&field_code)
169                .context("Failed to parse field definition")?;
170
171            let new_field = if let Fields::Named(ref nf) = dummy.fields {
172                nf.named.first()
173                    .context("No field found in definition")?
174                    .clone()
175            } else {
176                anyhow::bail!("Expected named field");
177            };
178
179            // Check if field already exists
180            let new_field_name = new_field.ident.as_ref()
181                .map(|i| i.to_string())
182                .context("Field must have a name")?;
183
184            if fields.named.iter().any(|f| {
185                f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
186            }) {
187                // Field already exists, skip adding
188                return Ok(false);
189            }
190            
191            // Determine insertion point
192            let insert_pos = match &op.position {
193                InsertPosition::First => {
194                    if let Some(first_field) = fields.named.first() {
195                        self.span_to_byte_offset(first_field.span().start())
196                    } else {
197                        // Empty struct, insert after the opening brace
198                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
199                        brace_pos + 1
200                    }
201                }
202                InsertPosition::Last => {
203                    if let Some(last_field) = fields.named.last() {
204                        let end = self.span_to_byte_offset(last_field.span().end());
205                        // Find the comma or end
206                        self.find_after_field_end(end)
207                    } else {
208                        // Empty struct
209                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
210                        brace_pos + 1
211                    }
212                }
213                InsertPosition::After(name) => {
214                    let field = fields.named.iter()
215                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
216                        .with_context(|| format!("Field '{}' not found", name))?;
217                    let end = self.span_to_byte_offset(field.span().end());
218                    self.find_after_field_end(end)
219                }
220                InsertPosition::Before(name) => {
221                    let field = fields.named.iter()
222                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
223                        .with_context(|| format!("Field '{}' not found", name))?;
224                    self.span_to_byte_offset(field.span().start())
225                }
226            };
227            
228            // Format the new field
229            let indent = self.get_indentation(insert_pos);
230            let field_str = Self::format_field(&new_field);
231            let insert_text = if matches!(op.position, InsertPosition::First) {
232                format!("\n{}{},", indent, field_str)
233            } else {
234                format!("\n{}{},", indent, field_str)
235            };
236
237            self.content.insert_str(insert_pos, &insert_text);
238            return Ok(true);
239        }
240        
241        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
242    }
243
244    pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
245        // Find the struct and clone it to avoid borrowing issues
246        let item_struct = self.syntax_tree.items.iter()
247            .find_map(|item| {
248                if let Item::Struct(s) = item {
249                    if s.ident == op.struct_name {
250                        return Some(s.clone());
251                    }
252                }
253                None
254            })
255            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
256
257        // Check if the struct matches the where filter (if specified)
258        if let Some(ref where_filter) = op.where_filter {
259            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
260                // Struct doesn't match filter - skip without error
261                return Ok(ModificationResult {
262                    changed: false,
263                    modified_nodes: vec![],
264                });
265            }
266        }
267
268        // Create backup of original struct before modification
269        let backup_node = BackupNode {
270            node_type: "ItemStruct".to_string(),
271            identifier: op.struct_name.clone(),
272            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
273            location: self.span_to_location(item_struct.span()),
274        };
275
276        let modified = self.replace_struct_field(&item_struct, op)?;
277
278        Ok(ModificationResult {
279            changed: modified,
280            modified_nodes: if modified { vec![backup_node] } else { vec![] },
281        })
282    }
283
284    fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
285        if let Fields::Named(ref fields) = item_struct.fields {
286            // Parse the new field definition to get the field name
287            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
288            let dummy: ItemStruct = parse_str(&field_code)
289                .context("Failed to parse field definition")?;
290
291            let new_field = if let Fields::Named(ref nf) = dummy.fields {
292                nf.named.first()
293                    .context("No field found in definition")?
294                    .clone()
295            } else {
296                anyhow::bail!("Expected named field");
297            };
298
299            // Extract the field name from the parsed field
300            let field_name = new_field.ident.as_ref()
301                .map(|i| i.to_string())
302                .context("Field must have a name")?;
303
304            // Find the existing field
305            let existing_field = fields.named.iter()
306                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
307                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
308
309            // Get the span of the existing field
310            let start = self.span_to_byte_offset(existing_field.span().start());
311            let end = self.span_to_byte_offset(existing_field.span().end());
312
313            // Format and replace the field
314            let new_field_str = Self::format_field(&new_field);
315
316            // Remove the old field and insert the new one
317            self.content.replace_range(start..end, &new_field_str);
318
319            return Ok(true);
320        }
321
322        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
323    }
324
325    pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
326        // Find the struct and clone it to avoid borrowing issues
327        let item_struct = self.syntax_tree.items.iter()
328            .find_map(|item| {
329                if let Item::Struct(s) = item {
330                    if s.ident == op.struct_name {
331                        return Some(s.clone());
332                    }
333                }
334                None
335            })
336            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
337
338        // Check if the struct matches the where filter (if specified)
339        if let Some(ref where_filter) = op.where_filter {
340            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
341                // Struct doesn't match filter - skip without error
342                return Ok(ModificationResult {
343                    changed: false,
344                    modified_nodes: vec![],
345                });
346            }
347        }
348
349        // Create backup of original struct before modification
350        let backup_node = BackupNode {
351            node_type: "ItemStruct".to_string(),
352            identifier: op.struct_name.clone(),
353            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
354            location: self.span_to_location(item_struct.span()),
355        };
356
357        if let Fields::Named(ref fields) = item_struct.fields {
358            // Find the field to remove
359            let field_to_remove = fields.named.iter()
360                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
361                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
362
363            // Get the span including the comma
364            let start = self.span_to_byte_offset(field_to_remove.span().start());
365            let mut end = self.span_to_byte_offset(field_to_remove.span().end());
366
367            // Find and include the comma and any trailing whitespace/newline
368            while end < self.content.len() {
369                match self.content.as_bytes()[end] as char {
370                    ',' => {
371                        end += 1;
372                        // Also consume the newline after the comma if present
373                        if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
374                            end += 1;
375                        }
376                        break;
377                    }
378                    ' ' | '\t' => end += 1,
379                    '\n' => {
380                        end += 1;
381                        break;
382                    }
383                    _ => break,
384                }
385            }
386
387            // Also need to remove leading whitespace/indentation on the same line
388            let mut line_start = start;
389            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
390                line_start -= 1;
391            }
392
393            // Check if there's only whitespace between line_start and start
394            let before_field = &self.content[line_start..start];
395            if before_field.trim().is_empty() {
396                // Remove the whole line
397                self.content.replace_range(line_start..end, "");
398            } else {
399                // Just remove the field and comma
400                self.content.replace_range(start..end, "");
401            }
402
403            return Ok(ModificationResult {
404                changed: true,
405                modified_nodes: vec![backup_node],
406            });
407        }
408
409        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
410    }
411
412    pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
413        // Parse the field name from field_def (e.g., "return_type: None" -> "return_type")
414        let field_name = op.field_def.split(':')
415            .next()
416            .map(|s| s.trim().to_string())
417            .context("Field definition must contain ':'")?;
418
419        // Collect backups of all struct literal expressions that will be modified
420        let backup_nodes = self.collect_struct_literal_backups(&op.struct_name);
421
422        // Use a visitor to find and modify all struct literals
423        let mut visitor = StructLiteralFieldAdder {
424            struct_name: op.struct_name.clone(),
425            field_def: op.field_def.clone(),
426            field_name,
427            position: op.position.clone(),
428            modified: false,
429        };
430
431        visitor.visit_file_mut(&mut self.syntax_tree);
432
433        if visitor.modified {
434            // Reformat the entire file for struct literals
435            self.content = prettyplease::unparse(&self.syntax_tree);
436            Ok(ModificationResult {
437                changed: true,
438                modified_nodes: backup_nodes,
439            })
440        } else {
441            Ok(ModificationResult {
442                changed: false,
443                modified_nodes: vec![],
444            })
445        }
446    }
447
448    /// Collect backups of all struct literal expressions for a given struct name
449    fn collect_struct_literal_backups(&self, struct_name: &str) -> Vec<BackupNode> {
450        use syn::visit::Visit;
451
452        struct LiteralCollector {
453            struct_name: String,
454            backups: Vec<BackupNode>,
455            counter: usize,
456        }
457
458        impl<'ast> Visit<'ast> for LiteralCollector {
459            fn visit_expr(&mut self, node: &'ast Expr) {
460                if let Expr::Struct(expr_struct) = node {
461                    if let Some(last_seg) = expr_struct.path.segments.last() {
462                        if last_seg.ident.to_string() == self.struct_name {
463                            self.backups.push(BackupNode {
464                                node_type: "ExprStruct".to_string(),
465                                identifier: format!("{}#{}", self.struct_name, self.counter),
466                                original_content: expr_struct.to_token_stream().to_string(),
467                                location: NodeLocation {
468                                    line: 0, // We don't have precise location info in visitor
469                                    column: 0,
470                                    end_line: 0,
471                                    end_column: 0,
472                                },
473                            });
474                            self.counter += 1;
475                        }
476                    }
477                }
478                syn::visit::visit_expr(self, node);
479            }
480        }
481
482        let mut collector = LiteralCollector {
483            struct_name: struct_name.to_string(),
484            backups: Vec::new(),
485            counter: 0,
486        };
487
488        collector.visit_file(&self.syntax_tree);
489        collector.backups
490    }
491
492    pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
493        // Find the enum and clone it to avoid borrowing issues
494        let item_enum = self.syntax_tree.items.iter()
495            .find_map(|item| {
496                if let Item::Enum(e) = item {
497                    if e.ident == op.enum_name {
498                        return Some(e.clone());
499                    }
500                }
501                None
502            })
503            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
504
505        // Check if the enum matches the where filter (if specified)
506        if let Some(ref where_filter) = op.where_filter {
507            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
508                // Enum doesn't match filter - skip without error
509                return Ok(ModificationResult {
510                    changed: false,
511                    modified_nodes: vec![],
512                });
513            }
514        }
515
516        // Create backup of original enum before modification
517        let backup_node = BackupNode {
518            node_type: "ItemEnum".to_string(),
519            identifier: op.enum_name.clone(),
520            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
521            location: self.span_to_location(item_enum.span()),
522        };
523
524        let modified = self.insert_enum_variant(&item_enum, op)?;
525
526        Ok(ModificationResult {
527            changed: modified,
528            modified_nodes: if modified { vec![backup_node] } else { vec![] },
529        })
530    }
531    
532    fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
533        // Parse the new variant
534        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
535        let dummy: ItemEnum = parse_str(&variant_code)
536            .context("Failed to parse variant definition")?;
537
538        let new_variant = dummy.variants.first()
539            .context("No variant found in definition")?
540            .clone();
541
542        // Check if variant already exists
543        let variant_name = new_variant.ident.to_string();
544        if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
545            // Variant already exists, skip adding
546            return Ok(false);
547        }
548
549        // Determine insertion point
550        let insert_pos = match &op.position {
551            InsertPosition::First => {
552                if let Some(first_var) = item_enum.variants.first() {
553                    self.span_to_byte_offset(first_var.span().start())
554                } else {
555                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
556                    brace_pos + 1
557                }
558            }
559            InsertPosition::Last => {
560                if let Some(last_var) = item_enum.variants.last() {
561                    let end = self.span_to_byte_offset(last_var.span().end());
562                    self.find_after_field_end(end)
563                } else {
564                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
565                    brace_pos + 1
566                }
567            }
568            InsertPosition::After(name) => {
569                let variant = item_enum.variants.iter()
570                    .find(|v| v.ident.to_string() == *name)
571                    .with_context(|| format!("Variant '{}' not found", name))?;
572                let end = self.span_to_byte_offset(variant.span().end());
573                self.find_after_field_end(end)
574            }
575            InsertPosition::Before(name) => {
576                let variant = item_enum.variants.iter()
577                    .find(|v| v.ident.to_string() == *name)
578                    .with_context(|| format!("Variant '{}' not found", name))?;
579                self.span_to_byte_offset(variant.span().start())
580            }
581        };
582        
583        let indent = self.get_indentation(insert_pos);
584        let variant_str = new_variant.to_token_stream().to_string();
585        let insert_text = format!("\n{}{},", indent, variant_str);
586        
587        self.content.insert_str(insert_pos, &insert_text);
588        Ok(true)
589    }
590
591    fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
592        // Find the enum and clone it
593        let item_enum = self.syntax_tree.items.iter()
594            .find_map(|item| {
595                if let Item::Enum(e) = item {
596                    if e.ident == op.enum_name {
597                        return Some(e.clone());
598                    }
599                }
600                None
601            })
602            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
603
604        // Check if the enum matches the where filter (if specified)
605        if let Some(ref where_filter) = op.where_filter {
606            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
607                // Enum doesn't match filter - skip without error
608                return Ok(ModificationResult {
609                    changed: false,
610                    modified_nodes: vec![],
611                });
612            }
613        }
614
615        // Create backup of original enum before modification
616        let backup_node = BackupNode {
617            node_type: "ItemEnum".to_string(),
618            identifier: op.enum_name.clone(),
619            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
620            location: self.span_to_location(item_enum.span()),
621        };
622
623        // Parse the new variant to get its name
624        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
625        let dummy: ItemEnum = parse_str(&variant_code)
626            .context("Failed to parse variant definition")?;
627
628        let new_variant = dummy.variants.first()
629            .context("No variant found in definition")?
630            .clone();
631
632        let variant_name = new_variant.ident.to_string();
633
634        // Find the existing variant
635        let existing_variant = item_enum.variants.iter()
636            .find(|v| v.ident.to_string() == variant_name)
637            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
638
639        // Get the span
640        let start = self.span_to_byte_offset(existing_variant.span().start());
641        let end = self.span_to_byte_offset(existing_variant.span().end());
642
643        // Format and replace
644        let variant_str = new_variant.to_token_stream().to_string();
645        self.content.replace_range(start..end, &variant_str);
646
647        Ok(ModificationResult {
648            changed: true,
649            modified_nodes: vec![backup_node],
650        })
651    }
652
653    pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
654        // Find the enum
655        let item_enum = self.syntax_tree.items.iter()
656            .find_map(|item| {
657                if let Item::Enum(e) = item {
658                    if e.ident == op.enum_name {
659                        return Some(e.clone());
660                    }
661                }
662                None
663            })
664            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
665
666        // Check if the enum matches the where filter (if specified)
667        if let Some(ref where_filter) = op.where_filter {
668            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
669                // Enum doesn't match filter - skip without error
670                return Ok(ModificationResult {
671                    changed: false,
672                    modified_nodes: vec![],
673                });
674            }
675        }
676
677        // Create backup of original enum before modification
678        let backup_node = BackupNode {
679            node_type: "ItemEnum".to_string(),
680            identifier: op.enum_name.clone(),
681            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
682            location: self.span_to_location(item_enum.span()),
683        };
684
685        // Find the variant to remove
686        let variant_to_remove = item_enum.variants.iter()
687            .find(|v| v.ident.to_string() == op.variant_name)
688            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
689
690        // Get the span including comma
691        let start = self.span_to_byte_offset(variant_to_remove.span().start());
692        let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
693
694        // Find and include the comma and trailing whitespace
695        while end < self.content.len() {
696            match self.content.as_bytes()[end] as char {
697                ',' => {
698                    end += 1;
699                    if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
700                        end += 1;
701                    }
702                    break;
703                }
704                ' ' | '\t' => end += 1,
705                '\n' => {
706                    end += 1;
707                    break;
708                }
709                _ => break,
710            }
711        }
712
713        // Remove leading whitespace on the line
714        let mut line_start = start;
715        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
716            line_start -= 1;
717        }
718
719        let before_variant = &self.content[line_start..start];
720        if before_variant.trim().is_empty() {
721            self.content.replace_range(line_start..end, "");
722        } else {
723            self.content.replace_range(start..end, "");
724        }
725
726        Ok(ModificationResult {
727            changed: true,
728            modified_nodes: vec![backup_node],
729        })
730    }
731
732    pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
733        if op.auto_detect {
734            // Auto-detect mode: find all missing enum variants
735            self.add_missing_match_arms(op)
736        } else {
737            // Normal mode: add a single match arm
738            self.add_single_match_arm(op)
739        }
740    }
741
742    fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
743        // Parse the pattern and body by creating a dummy match expression
744        let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
745        let expr: syn::Expr = parse_str(&dummy_match)
746            .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
747
748        // Extract the arm from the dummy match
749        let arm = if let syn::Expr::Match(match_expr) = expr {
750            match_expr.arms.into_iter().next()
751                .context("Failed to extract arm from dummy match")?
752        } else {
753            anyhow::bail!("Expected match expression");
754        };
755
756        // Collect backup of function before modification
757        let backup_node = if let Some(ref fn_name) = op.function_name {
758            self.get_function_backup(fn_name)?
759        } else {
760            // If no function specified, we'll backup all modified functions later
761            // For now, create a generic backup
762            BackupNode {
763                node_type: "Unknown".to_string(),
764                identifier: "match_expression".to_string(),
765                original_content: String::new(),
766                location: NodeLocation {
767                    line: 0,
768                    column: 0,
769                    end_line: 0,
770                    end_column: 0,
771                },
772            }
773        };
774
775        // Find and modify match expressions
776        let mut visitor = MatchArmAdder {
777            target_function: op.function_name.clone(),
778            arm_to_add: arm,
779            modified: false,
780            current_function: None,
781            modified_function: None,
782        };
783
784        visitor.visit_file_mut(&mut self.syntax_tree);
785
786        if visitor.modified {
787            // Replace just the modified function
788            self.replace_modified_functions(&visitor.modified_function)?;
789            Ok(ModificationResult {
790                changed: true,
791                modified_nodes: vec![backup_node],
792            })
793        } else {
794            Ok(ModificationResult {
795                changed: false,
796                modified_nodes: vec![],
797            })
798        }
799    }
800
801    /// Format a single item to string using prettyplease
802    fn unparse_item(&self, item: &Item) -> String {
803        let temp_file = syn::File {
804            shebang: None,
805            attrs: Vec::new(),
806            items: vec![item.clone()],
807        };
808        prettyplease::unparse(&temp_file).trim().to_string()
809    }
810
811    /// Get backup of a function before modification
812    fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
813        for item in &self.syntax_tree.items {
814            if let Item::Fn(f) = item {
815                if f.sig.ident == fn_name {
816                    return Ok(BackupNode {
817                        node_type: "ItemFn".to_string(),
818                        identifier: fn_name.to_string(),
819                        original_content: self.unparse_item(&Item::Fn(f.clone())),
820                        location: self.span_to_location(f.span()),
821                    });
822                }
823            }
824        }
825        anyhow::bail!("Function '{}' not found", fn_name)
826    }
827
828    fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
829        // Get the enum name
830        let enum_name = op.enum_name.as_ref()
831            .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
832
833        // Find all enum variants
834        let enum_variants = self.find_enum_variants(enum_name)?;
835
836        if enum_variants.is_empty() {
837            anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
838        }
839
840        // Find existing match arms
841        let existing_patterns = self.find_existing_match_patterns(&op.function_name);
842
843        // Determine missing variants
844        let mut missing_variants = Vec::new();
845        for variant in &enum_variants {
846            let pattern = format!("{}::{}", enum_name, variant);
847            let pattern_normalized = pattern.replace(" ", "");
848
849            let exists = existing_patterns.iter().any(|p| {
850                p.replace(" ", "") == pattern_normalized
851            });
852
853            if !exists {
854                missing_variants.push(variant.clone());
855            }
856        }
857
858        if missing_variants.is_empty() {
859            println!("All enum variants already covered in match expressions");
860            return Ok(ModificationResult {
861                changed: false,
862                modified_nodes: vec![],
863            });
864        }
865
866        // Get backup of function before modification
867        let backup_node = if let Some(ref fn_name) = op.function_name {
868            self.get_function_backup(fn_name)?
869        } else {
870            BackupNode {
871                node_type: "Unknown".to_string(),
872                identifier: "match_expression".to_string(),
873                original_content: String::new(),
874                location: NodeLocation {
875                    line: 0,
876                    column: 0,
877                    end_line: 0,
878                    end_column: 0,
879                },
880            }
881        };
882
883        // Add ALL missing match arms in one pass using a visitor
884        let mut arms_to_add = Vec::new();
885        for variant in &missing_variants {
886            let pattern = format!("{}::{}", enum_name, variant);
887            let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
888            let expr: syn::Expr = parse_str(&dummy_match)
889                .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
890
891            if let syn::Expr::Match(match_expr) = expr {
892                if let Some(arm) = match_expr.arms.into_iter().next() {
893                    arms_to_add.push((pattern.clone(), arm));
894                }
895            }
896        }
897
898        // Find and modify match expressions with all arms at once
899        let mut visitor = MultiMatchArmAdder {
900            target_function: op.function_name.clone(),
901            arms_to_add,
902            modified: false,
903            current_function: None,
904            modified_function: None,
905        };
906
907        visitor.visit_file_mut(&mut self.syntax_tree);
908
909        if visitor.modified {
910            // Print what was added
911            for variant in &missing_variants {
912                println!("Added match arm for: {}::{}", enum_name, variant);
913            }
914
915            // Replace just the modified function
916            self.replace_modified_functions(&visitor.modified_function)?;
917            Ok(ModificationResult {
918                changed: true,
919                modified_nodes: vec![backup_node],
920            })
921        } else {
922            Ok(ModificationResult {
923                changed: false,
924                modified_nodes: vec![],
925            })
926        }
927    }
928
929    fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
930        // Find the enum in the syntax tree
931        for item in &self.syntax_tree.items {
932            if let Item::Enum(e) = item {
933                if e.ident == enum_name {
934                    let variants: Vec<String> = e.variants.iter()
935                        .map(|v| v.ident.to_string())
936                        .collect();
937                    return Ok(variants);
938                }
939            }
940        }
941
942        Ok(Vec::new())
943    }
944
945    fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
946        use syn::visit::Visit;
947
948        struct PatternCollector {
949            target_function: Option<String>,
950            current_function: Option<String>,
951            patterns: Vec<String>,
952        }
953
954        impl<'ast> Visit<'ast> for PatternCollector {
955            fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
956                let prev_fn = self.current_function.clone();
957                self.current_function = Some(node.sig.ident.to_string());
958                syn::visit::visit_item_fn(self, node);
959                self.current_function = prev_fn;
960            }
961
962            fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
963                // Check if we're in the right function (if specified)
964                if let Some(ref target) = self.target_function {
965                    if self.current_function.as_ref() != Some(target) {
966                        syn::visit::visit_expr_match(self, node);
967                        return;
968                    }
969                }
970
971                // Collect all patterns
972                for arm in &node.arms {
973                    self.patterns.push(arm.pat.to_token_stream().to_string());
974                }
975
976                syn::visit::visit_expr_match(self, node);
977            }
978        }
979
980        let mut collector = PatternCollector {
981            target_function: function_name.clone(),
982            current_function: None,
983            patterns: Vec::new(),
984        };
985
986        collector.visit_file(&self.syntax_tree);
987        collector.patterns
988    }
989
990    pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
991        // Get backup of function before modification
992        let backup_node = if let Some(ref fn_name) = op.function_name {
993            self.get_function_backup(fn_name)?
994        } else {
995            BackupNode {
996                node_type: "Unknown".to_string(),
997                identifier: "match_expression".to_string(),
998                original_content: String::new(),
999                location: NodeLocation {
1000                    line: 0,
1001                    column: 0,
1002                    end_line: 0,
1003                    end_column: 0,
1004                },
1005            }
1006        };
1007
1008        // Parse the new body
1009        let new_body: syn::Expr = parse_str(&op.new_body)
1010            .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1011
1012        // Find and modify match expressions
1013        let mut visitor = MatchArmUpdater {
1014            target_function: op.function_name.clone(),
1015            pattern_to_match: op.pattern.clone(),
1016            new_body,
1017            modified: false,
1018            current_function: None,
1019            modified_function: None,
1020        };
1021
1022        visitor.visit_file_mut(&mut self.syntax_tree);
1023
1024        if visitor.modified {
1025            // Replace just the modified function
1026            self.replace_modified_functions(&visitor.modified_function)?;
1027            Ok(ModificationResult {
1028                changed: true,
1029                modified_nodes: vec![backup_node],
1030            })
1031        } else {
1032            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1033        }
1034    }
1035
1036    pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1037        // Get backup of function before modification
1038        let backup_node = if let Some(ref fn_name) = op.function_name {
1039            self.get_function_backup(fn_name)?
1040        } else {
1041            BackupNode {
1042                node_type: "Unknown".to_string(),
1043                identifier: "match_expression".to_string(),
1044                original_content: String::new(),
1045                location: NodeLocation {
1046                    line: 0,
1047                    column: 0,
1048                    end_line: 0,
1049                    end_column: 0,
1050                },
1051            }
1052        };
1053
1054        // Find and modify match expressions
1055        let mut visitor = MatchArmRemover {
1056            target_function: op.function_name.clone(),
1057            pattern_to_remove: op.pattern.clone(),
1058            modified: false,
1059            current_function: None,
1060            modified_function: None,
1061        };
1062
1063        visitor.visit_file_mut(&mut self.syntax_tree);
1064
1065        if visitor.modified {
1066            // Replace just the modified function
1067            self.replace_modified_functions(&visitor.modified_function)?;
1068            Ok(ModificationResult {
1069                changed: true,
1070                modified_nodes: vec![backup_node],
1071            })
1072        } else {
1073            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1074        }
1075    }
1076
1077    pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1078        // Parse the method definition
1079        let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1080        let dummy: syn::ItemImpl = parse_str(&method_code)
1081            .context("Failed to parse method definition")?;
1082
1083        let new_method = dummy.items.first()
1084            .context("No method found in definition")?
1085            .clone();
1086
1087        // Get the method name for idempotency check
1088        let method_name = match &new_method {
1089            syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1090            _ => anyhow::bail!("Only method definitions are supported"),
1091        };
1092
1093        // Find the impl block
1094        let impl_index = self.syntax_tree.items.iter().position(|item| {
1095            if let Item::Impl(impl_block) = item {
1096                // Check if this is the right impl block
1097                if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1098                    if let Some(segment) = type_path.path.segments.last() {
1099                        return segment.ident == op.target;
1100                    }
1101                }
1102            }
1103            false
1104        }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1105
1106        // Check if method already exists (idempotent)
1107        let impl_block = match &self.syntax_tree.items[impl_index] {
1108            Item::Impl(i) => i,
1109            _ => unreachable!(),
1110        };
1111
1112        let method_exists = impl_block.items.iter().any(|item| {
1113            if let syn::ImplItem::Fn(f) = item {
1114                f.sig.ident == method_name
1115            } else {
1116                false
1117            }
1118        });
1119
1120        if method_exists {
1121            return Ok(ModificationResult {
1122                changed: false,
1123                modified_nodes: vec![],
1124            });
1125        }
1126
1127        // Create backup of original impl block before modification
1128        let backup_node = BackupNode {
1129            node_type: "ItemImpl".to_string(),
1130            identifier: op.target.clone(),
1131            original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1132            location: self.span_to_location(impl_block.span()),
1133        };
1134
1135        // Get the span before modification
1136        let impl_span = impl_block.span();
1137
1138        // Add the method to the impl block
1139        match &mut self.syntax_tree.items[impl_index] {
1140            Item::Impl(impl_block) => {
1141                // Add based on position
1142                match &op.position {
1143                    InsertPosition::First => {
1144                        impl_block.items.insert(0, new_method);
1145                    }
1146                    InsertPosition::Last => {
1147                        impl_block.items.push(new_method);
1148                    }
1149                    InsertPosition::After(name) => {
1150                        let pos = impl_block.items.iter().position(|item| {
1151                            if let syn::ImplItem::Fn(f) = item {
1152                                f.sig.ident == name
1153                            } else {
1154                                false
1155                            }
1156                        }).with_context(|| format!("Method '{}' not found", name))?;
1157                        impl_block.items.insert(pos + 1, new_method);
1158                    }
1159                    InsertPosition::Before(name) => {
1160                        let pos = impl_block.items.iter().position(|item| {
1161                            if let syn::ImplItem::Fn(f) = item {
1162                                f.sig.ident == name
1163                            } else {
1164                                false
1165                            }
1166                        }).with_context(|| format!("Method '{}' not found", name))?;
1167                        impl_block.items.insert(pos, new_method);
1168                    }
1169                }
1170            }
1171            _ => unreachable!(),
1172        }
1173
1174        // Use prettyplease to format just this impl block
1175        self.replace_formatted_item(impl_index, impl_span)?;
1176
1177        Ok(ModificationResult {
1178            changed: true,
1179            modified_nodes: vec![backup_node],
1180        })
1181    }
1182
1183    pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1184        // Parse the use statement
1185        let use_code = format!("use {};", op.use_path);
1186        let use_item: syn::ItemUse = parse_str(&use_code)
1187            .context("Failed to parse use statement")?;
1188
1189        // Check if this use statement already exists (idempotent)
1190        let use_exists = self.syntax_tree.items.iter().any(|item| {
1191            if let Item::Use(existing_use) = item {
1192                // Compare the use trees
1193                existing_use.tree.to_token_stream().to_string() ==
1194                    use_item.tree.to_token_stream().to_string()
1195            } else {
1196                false
1197            }
1198        });
1199
1200        if use_exists {
1201            return Ok(ModificationResult {
1202                changed: false,
1203                modified_nodes: vec![],
1204            });
1205        }
1206
1207        // Create a simple backup for use statements (track by line position)
1208        let backup_node = BackupNode {
1209            node_type: "ItemUse".to_string(),
1210            identifier: op.use_path.clone(),
1211            original_content: format!("use {};", op.use_path),
1212            location: NodeLocation {
1213                line: 0,
1214                column: 0,
1215                end_line: 0,
1216                end_column: 0,
1217            },
1218        };
1219
1220        // Find the position to insert the use statement
1221        let insert_index = match &op.position {
1222            InsertPosition::First => 0,
1223            InsertPosition::Last => {
1224                // Find the last use statement
1225                self.syntax_tree.items.iter()
1226                    .rposition(|item| matches!(item, Item::Use(_)))
1227                    .map(|i| i + 1)
1228                    .unwrap_or(0)
1229            }
1230            InsertPosition::After(path) => {
1231                // Find the use statement matching the path
1232                let pos = self.syntax_tree.items.iter().position(|item| {
1233                    if let Item::Use(u) = item {
1234                        u.tree.to_token_stream().to_string().contains(path)
1235                    } else {
1236                        false
1237                    }
1238                }).with_context(|| format!("Use statement for '{}' not found", path))?;
1239                pos + 1
1240            }
1241            InsertPosition::Before(path) => {
1242                // Find the use statement matching the path
1243                self.syntax_tree.items.iter().position(|item| {
1244                    if let Item::Use(u) = item {
1245                        u.tree.to_token_stream().to_string().contains(path)
1246                    } else {
1247                        false
1248                    }
1249                }).with_context(|| format!("Use statement for '{}' not found", path))?
1250            }
1251        };
1252
1253        // Insert the use statement into the AST
1254        self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1255
1256        // Find the byte position in the source where we need to insert
1257        // We want to insert at the beginning of a line
1258        let insert_line_pos = if insert_index == 0 {
1259            // Insert at very beginning
1260            0
1261        } else {
1262            // Insert after the previous item
1263            let prev_item = &self.syntax_tree.items[insert_index - 1];
1264            let span = prev_item.span();
1265            let end_pos = self.span_to_byte_offset(span.end());
1266
1267            // Find the end of this line (where the newline is)
1268            let mut line_end = end_pos;
1269            while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1270                line_end += 1;
1271            }
1272            // Move past the newline to the start of the next line
1273            if line_end < self.content.len() {
1274                line_end + 1
1275            } else {
1276                // At end of file, add a newline first
1277                self.content.push('\n');
1278                self.content.len()
1279            }
1280        };
1281
1282        // Format the use statement
1283        let use_str = format!("use {};\n", op.use_path);
1284
1285        // Insert the use statement
1286        self.content.insert_str(insert_line_pos, &use_str);
1287
1288        Ok(ModificationResult {
1289            changed: true,
1290            modified_nodes: vec![backup_node],
1291        })
1292    }
1293
1294    pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1295        // Find the target item (struct or enum)
1296        let item_index = self.syntax_tree.items.iter().position(|item| {
1297            match (&op.target_type as &str, item) {
1298                ("struct", Item::Struct(s)) => s.ident == op.target_name,
1299                ("enum", Item::Enum(e)) => e.ident == op.target_name,
1300                _ => false,
1301            }
1302        }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1303
1304        // Get the item and check for existing derives
1305        let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1306            Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1307            Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1308            _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1309        };
1310
1311        // Check if the item matches the where filter (if specified)
1312        if let Some(ref where_filter) = op.where_filter {
1313            if !self.matches_where_filter(item_attrs, where_filter)? {
1314                // Item doesn't match filter - skip without error
1315                return Ok(ModificationResult {
1316                    changed: false,
1317                    modified_nodes: vec![],
1318                });
1319            }
1320        }
1321
1322        // Create backup of original item before modification
1323        let backup_node = BackupNode {
1324            node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1325            identifier: op.target_name.clone(),
1326            original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1327            location: self.span_to_location(item_span),
1328        };
1329
1330        // Filter out derives that already exist (idempotent)
1331        let new_derives: Vec<String> = op.derives.iter()
1332            .filter(|d| !existing_derives.contains(&d.to_string()))
1333            .cloned()
1334            .collect();
1335
1336        if new_derives.is_empty() {
1337            // All derives already exist
1338            return Ok(ModificationResult {
1339                changed: false,
1340                modified_nodes: vec![],
1341            });
1342        }
1343
1344        // Combine existing and new derives
1345        let mut all_derives = existing_derives;
1346        all_derives.extend(new_derives);
1347
1348        // Convert to string refs for the update function
1349        let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1350
1351        // Update the AST item's attributes
1352        match &mut self.syntax_tree.items[item_index] {
1353            Item::Struct(s) => {
1354                Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1355            }
1356            Item::Enum(e) => {
1357                Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1358            }
1359            _ => unreachable!(),
1360        }
1361
1362        // Use prettyplease to format just this item
1363        self.replace_formatted_item(item_index, item_span)?;
1364
1365        Ok(ModificationResult {
1366            changed: true,
1367            modified_nodes: vec![backup_node],
1368        })
1369    }
1370
1371    /// Replace an item in the content with a formatted version
1372    fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1373        // Get the item start and end positions from the original source
1374        let item_start_pos = self.span_to_byte_offset(original_span.start());
1375        let item_end_pos = self.span_to_byte_offset(original_span.end());
1376
1377        // Find the actual start (including attributes)
1378        let mut actual_start = item_start_pos;
1379
1380        // Search backwards for attributes
1381        let mut temp_pos = item_start_pos;
1382        while temp_pos > 0 {
1383            // Move to previous line
1384            temp_pos = temp_pos.saturating_sub(1);
1385            let mut line_start = temp_pos;
1386            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1387                line_start -= 1;
1388            }
1389
1390            let line = if temp_pos < self.content.len() {
1391                &self.content[line_start..temp_pos + 1]
1392            } else {
1393                &self.content[line_start..]
1394            };
1395            let trimmed = line.trim();
1396
1397            if trimmed.starts_with("#[") {
1398                actual_start = line_start;
1399                temp_pos = line_start;
1400            } else if trimmed.is_empty() {
1401                temp_pos = line_start;
1402            } else {
1403                break;
1404            }
1405
1406            if line_start == 0 {
1407                break;
1408            }
1409        }
1410
1411        // Create a temporary file with just this item for pretty formatting
1412        let item_clone = self.syntax_tree.items[item_index].clone();
1413        let temp_file = syn::File {
1414            shebang: None,
1415            attrs: Vec::new(),
1416            items: vec![item_clone],
1417        };
1418
1419        // Format the item using prettyplease
1420        let formatted = prettyplease::unparse(&temp_file);
1421        let formatted = formatted.trim();
1422
1423        // Replace in content
1424        self.content.replace_range(actual_start..item_end_pos, formatted);
1425
1426        Ok(())
1427    }
1428
1429    /// Extract existing derive traits from attributes - returns owned Strings
1430    fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1431        for attr in attrs {
1432            if attr.path().is_ident("derive") {
1433                if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1434                    let tokens_str = meta_list.tokens.to_string();
1435                    return tokens_str
1436                        .split(',')
1437                        .map(|s| s.trim().to_string())
1438                        .collect();
1439                }
1440            }
1441        }
1442        Vec::new()
1443    }
1444
1445    /// Check if an item matches the where filter criteria
1446    /// Supports filters like:
1447    /// - "derives_trait:Clone" - matches if item derives Clone
1448    /// - "derives_trait:Clone,Debug" - matches if item derives Clone OR Debug
1449    fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1450        // Parse the filter: "derives_trait:Clone,Debug"
1451        if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1452            let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1453            let existing_derives = Self::extract_derives(attrs);
1454
1455            // Check if ANY of the required traits are present
1456            for required_trait in required_traits {
1457                if existing_derives.iter().any(|d| d == required_trait) {
1458                    return Ok(true);
1459                }
1460            }
1461            return Ok(false);
1462        }
1463
1464        // Unknown filter type - default to match (don't break existing behavior)
1465        Ok(true)
1466    }
1467
1468    /// Update or create derive attribute in the attribute list
1469    fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1470        let derive_str = derives.join(", ");
1471
1472        // Parse a dummy struct with the derive to extract the attribute
1473        let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1474        let parsed: syn::ItemStruct = parse_str(&dummy)
1475            .context("Failed to parse derive attribute")?;
1476
1477        let new_attr = parsed.attrs.into_iter()
1478            .find(|a| a.path().is_ident("derive"))
1479            .context("Failed to extract derive attribute")?;
1480
1481        // Find existing derive attribute and replace it
1482        if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1483            attrs[pos] = new_attr;
1484        } else {
1485            // Add new derive attribute at the beginning
1486            attrs.insert(0, new_attr);
1487        }
1488
1489        Ok(())
1490    }
1491
1492    /// Replace the modified function(s) in the content with formatted versions
1493    fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1494        // If no specific function was targeted, format the entire file
1495        if modified_function.is_none() {
1496            self.content = prettyplease::unparse(&self.syntax_tree);
1497            return Ok(());
1498        }
1499
1500        // Parse the ORIGINAL content to get the correct spans
1501        let original_syntax_tree: File = syn::parse_str(&self.content)
1502            .context("Failed to re-parse original content")?;
1503
1504        let function_name = modified_function.as_ref().unwrap();
1505
1506        // Find the function in the ORIGINAL syntax tree to get correct byte positions
1507        let original_fn = original_syntax_tree.items.iter()
1508            .find_map(|item| {
1509                if let Item::Fn(f) = item {
1510                    if f.sig.ident == function_name {
1511                        return Some(f.clone());
1512                    }
1513                }
1514                None
1515            })
1516            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1517
1518        // Get the span of the original function (these are the correct byte positions)
1519        let start = self.span_to_byte_offset(original_fn.span().start());
1520        let end = self.span_to_byte_offset(original_fn.span().end());
1521
1522        // Find the MODIFIED function in the modified syntax tree
1523        let modified_fn = self.syntax_tree.items.iter()
1524            .find_map(|item| {
1525                if let Item::Fn(f) = item {
1526                    if f.sig.ident == function_name {
1527                        return Some(f.clone());
1528                    }
1529                }
1530                None
1531            })
1532            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1533
1534        // Format just the modified function using prettyplease
1535        let dummy_file = syn::File {
1536            shebang: None,
1537            attrs: Vec::new(),
1538            items: vec![Item::Fn(modified_fn)],
1539        };
1540
1541        let formatted_fn = prettyplease::unparse(&dummy_file);
1542
1543        // Extract just the function (remove any extra newlines at start/end)
1544        let formatted_fn = formatted_fn.trim();
1545
1546        // Replace the function in the original content using original spans
1547        self.content.replace_range(start..end, formatted_fn);
1548
1549        Ok(())
1550    }
1551    
1552    fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1553        let line_idx = pos.line.saturating_sub(1);
1554        if line_idx < self.line_offsets.len() {
1555            self.line_offsets[line_idx] + pos.column
1556        } else {
1557            self.content.len()
1558        }
1559    }
1560    
1561    fn find_after_field_end(&self, pos: usize) -> usize {
1562        // Look for comma or newline after the field
1563        let mut i = pos;
1564        while i < self.content.len() {
1565            match self.content.as_bytes()[i] as char {
1566                ',' => return i + 1,
1567                '\n' => return i + 1,
1568                _ => i += 1,
1569            }
1570        }
1571        pos
1572    }
1573    
1574    fn get_indentation(&self, pos: usize) -> String {
1575        // Find the start of the current line
1576        let mut line_start = pos;
1577        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1578            line_start -= 1;
1579        }
1580        
1581        // Count spaces/tabs at the start of the line
1582        let mut indent = String::new();
1583        let mut i = line_start;
1584        while i < self.content.len() {
1585            match self.content.as_bytes()[i] as char {
1586                ' ' | '\t' => {
1587                    indent.push(self.content.as_bytes()[i] as char);
1588                    i += 1;
1589                }
1590                _ => break,
1591            }
1592        }
1593        
1594        // If we're inserting in an empty struct/enum, add default indentation
1595        if indent.is_empty() {
1596            "    ".to_string()
1597        } else {
1598            indent
1599        }
1600    }
1601    
1602    pub fn to_string(&self) -> String {
1603        self.content.clone()
1604    }
1605
1606    /// Inspect and list AST nodes (e.g., struct literals) in the file
1607    pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1608        use syn::visit::Visit;
1609        use crate::operations::InspectResult;
1610
1611        let mut results = Vec::new();
1612
1613        match node_type {
1614            "struct-literal" => {
1615                // Find all struct literal expressions
1616                struct StructLiteralVisitor<'a> {
1617                    results: &'a mut Vec<InspectResult>,
1618                    name_filter: Option<&'a str>,
1619                    editor: &'a RustEditor,
1620                }
1621
1622                impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1623                    fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1624                        // Extract the struct name from the path
1625                        let struct_name = if let Some(ident) = node.path.get_ident() {
1626                            ident.to_string()
1627                        } else {
1628                            // Handle paths like "module::StructName"
1629                            node.path.segments.last()
1630                                .map(|seg| seg.ident.to_string())
1631                                .unwrap_or_default()
1632                        };
1633
1634                        // Apply name filter if specified
1635                        if let Some(filter) = self.name_filter {
1636                            if struct_name != filter {
1637                                syn::visit::visit_expr_struct(self, node);
1638                                return;
1639                            }
1640                        }
1641
1642                        // Format the struct literal
1643                        let snippet = self.editor.format_expr_struct(node);
1644                        let location = self.editor.span_to_location(node.span());
1645
1646                        self.results.push(InspectResult {
1647                            file_path: String::new(), // Will be filled in by caller
1648                            node_type: "ExprStruct".to_string(),
1649                            identifier: struct_name,
1650                            location,
1651                            snippet,
1652                        });
1653
1654                        // Continue visiting nested expressions
1655                        syn::visit::visit_expr_struct(self, node);
1656                    }
1657                }
1658
1659                let mut visitor = StructLiteralVisitor {
1660                    results: &mut results,
1661                    name_filter,
1662                    editor: self,
1663                };
1664
1665                // Visit all items in the file
1666                for item in &self.syntax_tree.items {
1667                    syn::visit::visit_item(&mut visitor, item);
1668                }
1669            }
1670            "match-arm" => {
1671                // Find all match arms
1672                struct MatchArmVisitor<'a> {
1673                    results: &'a mut Vec<InspectResult>,
1674                    pattern_filter: Option<&'a str>,
1675                    editor: &'a RustEditor,
1676                }
1677
1678                impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1679                    fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1680                        // Iterate through all arms in this match expression
1681                        for arm in &node.arms {
1682                            // Convert pattern to string for matching
1683                            let pat = &arm.pat;
1684                            let pattern_str = quote::quote!(#pat).to_string();
1685
1686                            // Apply pattern filter if specified
1687                            if let Some(filter) = self.pattern_filter {
1688                                // Normalize both for comparison (remove spaces)
1689                                let normalized_pattern = pattern_str.replace(" ", "");
1690                                let normalized_filter = filter.replace(" ", "");
1691
1692                                if !normalized_pattern.contains(&normalized_filter) {
1693                                    continue;
1694                                }
1695                            }
1696
1697                            // Format the match arm (pattern => body)
1698                            let snippet = self.editor.format_match_arm(arm);
1699                            let location = self.editor.span_to_location(arm.span());
1700
1701                            self.results.push(InspectResult {
1702                                file_path: String::new(), // Will be filled in by caller
1703                                node_type: "MatchArm".to_string(),
1704                                identifier: pattern_str.replace(" ", ""),
1705                                location,
1706                                snippet,
1707                            });
1708                        }
1709
1710                        // Continue visiting nested expressions
1711                        syn::visit::visit_expr_match(self, node);
1712                    }
1713                }
1714
1715                let mut visitor = MatchArmVisitor {
1716                    results: &mut results,
1717                    pattern_filter: name_filter,
1718                    editor: self,
1719                };
1720
1721                // Visit all items in the file
1722                for item in &self.syntax_tree.items {
1723                    syn::visit::visit_item(&mut visitor, item);
1724                }
1725            }
1726            "enum-usage" => {
1727                // Find all enum variant usages (paths like Operator::Error)
1728                struct EnumUsageVisitor<'a> {
1729                    results: &'a mut Vec<InspectResult>,
1730                    path_filter: Option<&'a str>,
1731                    editor: &'a RustEditor,
1732                }
1733
1734                impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1735                    fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1736                        // Convert path to string
1737                        let path = &node.path;
1738                        let path_str = quote::quote!(#path).to_string();
1739
1740                        // Apply path filter if specified
1741                        if let Some(filter) = self.path_filter {
1742                            // Normalize both for comparison (remove spaces)
1743                            let normalized_path = path_str.replace(" ", "");
1744                            let normalized_filter = filter.replace(" ", "");
1745
1746                            if !normalized_path.contains(&normalized_filter) {
1747                                syn::visit::visit_expr_path(self, node);
1748                                return;
1749                            }
1750                        }
1751
1752                        // Format the path expression
1753                        let snippet = self.editor.format_expr_path(node);
1754                        let location = self.editor.span_to_location(node.span());
1755
1756                        self.results.push(InspectResult {
1757                            file_path: String::new(), // Will be filled in by caller
1758                            node_type: "ExprPath".to_string(),
1759                            identifier: path_str.replace(" ", ""),
1760                            location,
1761                            snippet,
1762                        });
1763
1764                        // Continue visiting nested expressions
1765                        syn::visit::visit_expr_path(self, node);
1766                    }
1767                }
1768
1769                let mut visitor = EnumUsageVisitor {
1770                    results: &mut results,
1771                    path_filter: name_filter,
1772                    editor: self,
1773                };
1774
1775                // Visit all items in the file
1776                for item in &self.syntax_tree.items {
1777                    syn::visit::visit_item(&mut visitor, item);
1778                }
1779            }
1780            "function-call" => {
1781                // Find all function call expressions
1782                struct FunctionCallVisitor<'a> {
1783                    results: &'a mut Vec<InspectResult>,
1784                    name_filter: Option<&'a str>,
1785                    editor: &'a RustEditor,
1786                }
1787
1788                impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
1789                    fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1790                        // Extract function name from the call expression
1791                        let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1792                            // Get the last segment of the path as the function name
1793                            expr_path.path.segments.last()
1794                                .map(|seg| seg.ident.to_string())
1795                                .unwrap_or_default()
1796                        } else {
1797                            // For other expression types, use quote to convert to string
1798                            quote::quote!(#node.func).to_string()
1799                        };
1800
1801                        // Apply name filter if specified
1802                        if let Some(filter) = self.name_filter {
1803                            if func_name != filter {
1804                                syn::visit::visit_expr_call(self, node);
1805                                return;
1806                            }
1807                        }
1808
1809                        // Format the function call
1810                        let snippet = self.editor.format_expr_call(node);
1811                        let location = self.editor.span_to_location(node.span());
1812
1813                        self.results.push(InspectResult {
1814                            file_path: String::new(), // Will be filled in by caller
1815                            node_type: "ExprCall".to_string(),
1816                            identifier: func_name,
1817                            location,
1818                            snippet,
1819                        });
1820
1821                        // Continue visiting nested expressions
1822                        syn::visit::visit_expr_call(self, node);
1823                    }
1824                }
1825
1826                let mut visitor = FunctionCallVisitor {
1827                    results: &mut results,
1828                    name_filter,
1829                    editor: self,
1830                };
1831
1832                // Visit all items in the file
1833                for item in &self.syntax_tree.items {
1834                    syn::visit::visit_item(&mut visitor, item);
1835                }
1836            }
1837            "method-call" => {
1838                // Find all method call expressions
1839                struct MethodCallVisitor<'a> {
1840                    results: &'a mut Vec<InspectResult>,
1841                    name_filter: Option<&'a str>,
1842                    editor: &'a RustEditor,
1843                }
1844
1845                impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1846                    fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1847                        // Extract method name
1848                        let method_name = node.method.to_string();
1849
1850                        // Apply name filter if specified
1851                        if let Some(filter) = self.name_filter {
1852                            if method_name != filter {
1853                                syn::visit::visit_expr_method_call(self, node);
1854                                return;
1855                            }
1856                        }
1857
1858                        // Format the method call
1859                        let snippet = self.editor.format_expr_method_call(node);
1860                        let location = self.editor.span_to_location(node.span());
1861
1862                        self.results.push(InspectResult {
1863                            file_path: String::new(), // Will be filled in by caller
1864                            node_type: "ExprMethodCall".to_string(),
1865                            identifier: method_name,
1866                            location,
1867                            snippet,
1868                        });
1869
1870                        // Continue visiting nested expressions
1871                        syn::visit::visit_expr_method_call(self, node);
1872                    }
1873                }
1874
1875                let mut visitor = MethodCallVisitor {
1876                    results: &mut results,
1877                    name_filter,
1878                    editor: self,
1879                };
1880
1881                // Visit all items in the file
1882                for item in &self.syntax_tree.items {
1883                    syn::visit::visit_item(&mut visitor, item);
1884                }
1885            }
1886            "identifier" => {
1887                // Find all identifier references
1888                struct IdentifierVisitor<'a> {
1889                    results: &'a mut Vec<InspectResult>,
1890                    name_filter: Option<&'a str>,
1891                    editor: &'a RustEditor,
1892                }
1893
1894                impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
1895                    fn visit_ident(&mut self, node: &'ast syn::Ident) {
1896                        // Extract identifier name
1897                        let ident_name = node.to_string();
1898
1899                        // Apply name filter if specified
1900                        if let Some(filter) = self.name_filter {
1901                            if ident_name != filter {
1902                                syn::visit::visit_ident(self, node);
1903                                return;
1904                            }
1905                        }
1906
1907                        // Format the identifier
1908                        let snippet = self.editor.format_ident(node);
1909                        let location = self.editor.span_to_location(node.span());
1910
1911                        self.results.push(InspectResult {
1912                            file_path: String::new(), // Will be filled in by caller
1913                            node_type: "Ident".to_string(),
1914                            identifier: ident_name,
1915                            location,
1916                            snippet,
1917                        });
1918
1919                        // Continue visiting
1920                        syn::visit::visit_ident(self, node);
1921                    }
1922                }
1923
1924                let mut visitor = IdentifierVisitor {
1925                    results: &mut results,
1926                    name_filter,
1927                    editor: self,
1928                };
1929
1930                // Visit all items in the file
1931                for item in &self.syntax_tree.items {
1932                    syn::visit::visit_item(&mut visitor, item);
1933                }
1934            }
1935            "type-ref" => {
1936                // Find all type path usages
1937                struct TypeRefVisitor<'a> {
1938                    results: &'a mut Vec<InspectResult>,
1939                    name_filter: Option<&'a str>,
1940                    editor: &'a RustEditor,
1941                }
1942
1943                impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
1944                    fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
1945                        // Extract type name (last segment of path)
1946                        let type_name = node.path.segments.last()
1947                            .map(|seg| seg.ident.to_string())
1948                            .unwrap_or_default();
1949
1950                        // Apply name filter if specified
1951                        if let Some(filter) = self.name_filter {
1952                            if type_name != filter {
1953                                syn::visit::visit_type_path(self, node);
1954                                return;
1955                            }
1956                        }
1957
1958                        // Format the type path
1959                        let snippet = self.editor.format_type_path(node);
1960                        let location = self.editor.span_to_location(node.span());
1961
1962                        // Get full path for identifier
1963                        let path = &node.path;
1964                        let path_str = quote::quote!(#path).to_string();
1965
1966                        self.results.push(InspectResult {
1967                            file_path: String::new(), // Will be filled in by caller
1968                            node_type: "TypePath".to_string(),
1969                            identifier: path_str.replace(" ", ""),
1970                            location,
1971                            snippet,
1972                        });
1973
1974                        // Continue visiting
1975                        syn::visit::visit_type_path(self, node);
1976                    }
1977                }
1978
1979                let mut visitor = TypeRefVisitor {
1980                    results: &mut results,
1981                    name_filter,
1982                    editor: self,
1983                };
1984
1985                // Visit all items in the file
1986                for item in &self.syntax_tree.items {
1987                    syn::visit::visit_item(&mut visitor, item);
1988                }
1989            }
1990            "macro-call" => {
1991                // Find all macro call expressions
1992                struct MacroCallVisitor<'a> {
1993                    results: &'a mut Vec<InspectResult>,
1994                    name_filter: Option<&'a str>,
1995                    editor: &'a RustEditor,
1996                }
1997
1998                impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
1999                    fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2000                        // Extract macro name from the path
2001                        let macro_name = node.mac.path.segments.last()
2002                            .map(|seg| seg.ident.to_string())
2003                            .unwrap_or_default();
2004
2005                        // Apply name filter if specified
2006                        if let Some(filter) = self.name_filter {
2007                            if macro_name != filter {
2008                                syn::visit::visit_expr_macro(self, node);
2009                                return;
2010                            }
2011                        }
2012
2013                        // Format the macro call
2014                        let snippet = self.editor.format_expr_macro(node);
2015                        let location = self.editor.span_to_location(node.span());
2016
2017                        self.results.push(InspectResult {
2018                            file_path: String::new(), // Will be filled in by caller
2019                            node_type: "ExprMacro".to_string(),
2020                            identifier: macro_name,
2021                            location,
2022                            snippet,
2023                        });
2024
2025                        // Continue visiting nested expressions
2026                        syn::visit::visit_expr_macro(self, node);
2027                    }
2028
2029                    fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2030                        // Also catch macro calls at statement level (e.g., println! as statement)
2031                        if let syn::Stmt::Macro(macro_stmt) = node {
2032                            let macro_name = macro_stmt.mac.path.segments.last()
2033                                .map(|seg| seg.ident.to_string())
2034                                .unwrap_or_default();
2035
2036                            // Apply name filter if specified
2037                            if let Some(filter) = self.name_filter {
2038                                if macro_name != filter {
2039                                    syn::visit::visit_stmt(self, node);
2040                                    return;
2041                                }
2042                            }
2043
2044                            // Format the macro call
2045                            let snippet = self.editor.format_stmt_macro(macro_stmt);
2046                            let location = self.editor.span_to_location(macro_stmt.span());
2047
2048                            self.results.push(InspectResult {
2049                                file_path: String::new(), // Will be filled in by caller
2050                                node_type: "StmtMacro".to_string(),
2051                                identifier: macro_name,
2052                                location,
2053                                snippet,
2054                            });
2055                        }
2056
2057                        // Continue visiting
2058                        syn::visit::visit_stmt(self, node);
2059                    }
2060                }
2061
2062                let mut visitor = MacroCallVisitor {
2063                    results: &mut results,
2064                    name_filter,
2065                    editor: self,
2066                };
2067
2068                // Visit all items in the file
2069                for item in &self.syntax_tree.items {
2070                    syn::visit::visit_item(&mut visitor, item);
2071                }
2072            }
2073            _ => anyhow::bail!("Unsupported node type: {}", node_type),
2074        }
2075
2076        Ok(results)
2077    }
2078
2079    /// Format an ExprStruct node as a string - extracts original source
2080    fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
2081        // Extract the original source code from the file content using the span
2082        let start = self.span_to_byte_offset(expr.span().start());
2083        let end = self.span_to_byte_offset(expr.span().end());
2084
2085        // Get the original text and collapse to single line
2086        let original = &self.content[start..end];
2087
2088        // Replace multiple whitespace/newlines with single space for single-line format
2089        original.split_whitespace().collect::<Vec<_>>().join(" ")
2090    }
2091
2092    /// Format a match arm as a string - extracts original source
2093    fn format_match_arm(&self, arm: &syn::Arm) -> String {
2094        // Extract the original source code from the file content using the span
2095        let start = self.span_to_byte_offset(arm.span().start());
2096        let end = self.span_to_byte_offset(arm.span().end());
2097
2098        // Get the original text and collapse to single line
2099        let original = &self.content[start..end];
2100
2101        // Replace multiple whitespace/newlines with single space for single-line format
2102        original.split_whitespace().collect::<Vec<_>>().join(" ")
2103    }
2104
2105    /// Format an ExprPath node as a string - extracts original source
2106    fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2107        // Extract the original source code from the file content using the span
2108        let start = self.span_to_byte_offset(expr.span().start());
2109        let end = self.span_to_byte_offset(expr.span().end());
2110
2111        // Get the original text and collapse to single line
2112        let original = &self.content[start..end];
2113
2114        // Replace multiple whitespace/newlines with single space for single-line format
2115        original.split_whitespace().collect::<Vec<_>>().join(" ")
2116    }
2117
2118    /// Format an ExprCall node as a string - extracts original source
2119    fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
2120        // Extract the original source code from the file content using the span
2121        let start = self.span_to_byte_offset(expr.span().start());
2122        let end = self.span_to_byte_offset(expr.span().end());
2123
2124        // Get the original text and collapse to single line
2125        let original = &self.content[start..end];
2126
2127        // Replace multiple whitespace/newlines with single space for single-line format
2128        original.split_whitespace().collect::<Vec<_>>().join(" ")
2129    }
2130
2131    /// Format an ExprMethodCall node as a string - extracts original source
2132    fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2133        // Extract the original source code from the file content using the span
2134        let start = self.span_to_byte_offset(expr.span().start());
2135        let end = self.span_to_byte_offset(expr.span().end());
2136
2137        // Get the original text and collapse to single line
2138        let original = &self.content[start..end];
2139
2140        // Replace multiple whitespace/newlines with single space for single-line format
2141        original.split_whitespace().collect::<Vec<_>>().join(" ")
2142    }
2143
2144    /// Format an Ident node as a string - just return the identifier
2145    fn format_ident(&self, ident: &syn::Ident) -> String {
2146        ident.to_string()
2147    }
2148
2149    /// Format a TypePath node as a string - extracts original source
2150    fn format_type_path(&self, ty: &syn::TypePath) -> String {
2151        // Extract the original source code from the file content using the span
2152        let start = self.span_to_byte_offset(ty.span().start());
2153        let end = self.span_to_byte_offset(ty.span().end());
2154
2155        // Get the original text and collapse to single line
2156        let original = &self.content[start..end];
2157
2158        // Replace multiple whitespace/newlines with single space for single-line format
2159        original.split_whitespace().collect::<Vec<_>>().join(" ")
2160    }
2161
2162    /// Format an ExprMacro node as a string - extracts original source
2163    fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
2164        // Extract the original source code from the file content using the span
2165        let start = self.span_to_byte_offset(expr.span().start());
2166        let end = self.span_to_byte_offset(expr.span().end());
2167
2168        // Get the original text and collapse to single line
2169        let original = &self.content[start..end];
2170
2171        // Replace multiple whitespace/newlines with single space for single-line format
2172        original.split_whitespace().collect::<Vec<_>>().join(" ")
2173    }
2174
2175    /// Format a StmtMacro node as a string - extracts original source
2176    fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
2177        // Extract the original source code from the file content using the span
2178        let start = self.span_to_byte_offset(stmt.span().start());
2179        let end = self.span_to_byte_offset(stmt.span().end());
2180
2181        // Get the original text and collapse to single line
2182        let original = &self.content[start..end];
2183
2184        // Replace multiple whitespace/newlines with single space for single-line format
2185        original.split_whitespace().collect::<Vec<_>>().join(" ")
2186    }
2187
2188    /// Find the index of an item by type and name
2189    #[allow(dead_code)]
2190    pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2191        for (index, item) in self.syntax_tree.items.iter().enumerate() {
2192            match (node_type, item) {
2193                ("struct", Item::Struct(s)) if s.ident == name => {
2194                    return Ok(index);
2195                }
2196                ("enum", Item::Enum(e)) if e.ident == name => {
2197                    return Ok(index);
2198                }
2199                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2200                    return Ok(index);
2201                }
2202                ("impl", Item::Impl(impl_block)) => {
2203                    // For impl blocks, match on the self_ty
2204                    if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2205                        if let Some(segment) = type_path.path.segments.last() {
2206                            if segment.ident == name {
2207                                return Ok(index);
2208                            }
2209                        }
2210                    }
2211                }
2212                _ => {}
2213            }
2214        }
2215
2216        anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2217    }
2218
2219    /// Replace an item at a specific index with a new item
2220    #[allow(dead_code)]
2221    pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2222        if index >= self.syntax_tree.items.len() {
2223            anyhow::bail!("Index {} out of bounds", index);
2224        }
2225
2226        // Replace the item in the syntax tree
2227        self.syntax_tree.items[index] = new_item;
2228
2229        // Reformat the entire file using prettyplease
2230        self.content = prettyplease::unparse(&self.syntax_tree);
2231
2232        // Recompute line offsets
2233        self.line_offsets = Self::compute_line_offsets(&self.content);
2234
2235        Ok(())
2236    }
2237
2238    pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2239        let mut locations = Vec::new();
2240        
2241        for item in &self.syntax_tree.items {
2242            match (node_type, item) {
2243                ("struct", Item::Struct(s)) if s.ident == name => {
2244                    locations.push(self.span_to_location(s.span()));
2245                }
2246                ("enum", Item::Enum(e)) if e.ident == name => {
2247                    locations.push(self.span_to_location(e.span()));
2248                }
2249                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2250                    locations.push(self.span_to_location(f.span()));
2251                }
2252                _ => {}
2253            }
2254        }
2255        
2256        if locations.is_empty() {
2257            anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2258        }
2259        
2260        Ok(locations)
2261    }
2262    
2263    fn span_to_location(&self, span: Span) -> NodeLocation {
2264        let start = span.start();
2265        let end = span.end();
2266
2267        NodeLocation {
2268            line: start.line,
2269            column: start.column,
2270            end_line: end.line,
2271            end_column: end.column,
2272        }
2273    }
2274
2275    /// Generic transform operation - find matching nodes and apply action
2276    pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
2277        use crate::operations::{InspectResult, TransformAction};
2278
2279        // First, use inspect to find all matching nodes
2280        let matches = self.inspect(&op.node_type, op.name_filter.as_deref())?;
2281
2282        // Apply content filter if specified
2283        let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
2284            matches.into_iter()
2285                .filter(|m| m.snippet.contains(content_filter))
2286                .collect()
2287        } else {
2288            matches
2289        };
2290
2291        if filtered_matches.is_empty() {
2292            return Ok(ModificationResult {
2293                changed: false,
2294                modified_nodes: vec![],
2295            });
2296        }
2297
2298        // Now apply the transformation action to each match
2299        // We need to work backwards through the file to avoid offset issues
2300        let mut sorted_matches = filtered_matches;
2301        sorted_matches.sort_by(|a, b| {
2302            b.location.line.cmp(&a.location.line)
2303                .then(b.location.column.cmp(&a.location.column))
2304        });
2305
2306        let mut modified_nodes = Vec::new();
2307
2308        for match_result in &sorted_matches {
2309            // Create backup node
2310            let backup_node = BackupNode {
2311                node_type: match_result.node_type.clone(),
2312                identifier: match_result.identifier.clone(),
2313                original_content: match_result.snippet.clone(),
2314                location: match_result.location.clone(),
2315            };
2316
2317            // Find the byte offsets for this node
2318            let start_offset = self.line_column_to_byte_offset(
2319                match_result.location.line,
2320                match_result.location.column
2321            )?;
2322            let end_offset = self.line_column_to_byte_offset(
2323                match_result.location.end_line,
2324                match_result.location.end_column
2325            )?;
2326
2327            // Extract the original text
2328            let original_text = &self.content[start_offset..end_offset];
2329
2330            // Apply the action
2331            let replacement = match &op.action {
2332                TransformAction::Comment => {
2333                    // Comment out the code
2334                    format!("// {}", original_text.replace("\n", "\n// "))
2335                }
2336                TransformAction::Remove => {
2337                    // Remove the entire node
2338                    String::new()
2339                }
2340                TransformAction::Replace { with } => {
2341                    // Replace with provided code
2342                    with.clone()
2343                }
2344            };
2345
2346            // Replace in content
2347            self.content.replace_range(start_offset..end_offset, &replacement);
2348
2349            // Recompute line offsets after each change
2350            self.line_offsets = Self::compute_line_offsets(&self.content);
2351
2352            modified_nodes.push(backup_node);
2353        }
2354
2355        // Re-parse the content if we made changes
2356        if !modified_nodes.is_empty() {
2357            // Don't reparse for now - we're doing text-level operations
2358            // self.syntax_tree = syn::parse_str(&self.content)
2359            //     .context("Failed to re-parse content after transformation")?;
2360        }
2361
2362        Ok(ModificationResult {
2363            changed: !modified_nodes.is_empty(),
2364            modified_nodes,
2365        })
2366    }
2367
2368    /// Convert line/column to byte offset
2369    fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
2370        if line == 0 || line > self.line_offsets.len() {
2371            anyhow::bail!("Line {} out of range", line);
2372        }
2373
2374        let line_start = self.line_offsets[line - 1];
2375        Ok(line_start + column)
2376    }
2377}
2378
2379// Visitor for adding match arms
2380struct MatchArmAdder {
2381    target_function: Option<String>,
2382    arm_to_add: Arm,
2383    modified: bool,
2384    current_function: Option<String>,
2385    modified_function: Option<String>,
2386}
2387
2388impl VisitMut for MatchArmAdder {
2389    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2390        let prev_fn = self.current_function.clone();
2391        self.current_function = Some(node.sig.ident.to_string());
2392
2393        // Continue visiting nested items
2394        syn::visit_mut::visit_item_fn_mut(self, node);
2395
2396        self.current_function = prev_fn;
2397    }
2398
2399    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2400        // Check if we're in the right function (if specified)
2401        if let Some(ref target) = self.target_function {
2402            if self.current_function.as_ref() != Some(target) {
2403                // Continue visiting nested expressions
2404                syn::visit_mut::visit_expr_match_mut(self, node);
2405                return;
2406            }
2407        }
2408
2409        // Check if the pattern already exists (idempotent)
2410        let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2411        let already_exists = node.arms.iter().any(|arm| {
2412            arm.pat.to_token_stream().to_string() == pattern_str
2413        });
2414
2415        if !already_exists {
2416            // Add the arm to the end
2417            node.arms.push(self.arm_to_add.clone());
2418            self.modified = true;
2419            self.modified_function = self.current_function.clone();
2420        }
2421
2422        // Continue visiting nested expressions
2423        syn::visit_mut::visit_expr_match_mut(self, node);
2424    }
2425}
2426
2427// Visitor for updating match arms
2428struct MatchArmUpdater {
2429    target_function: Option<String>,
2430    pattern_to_match: String,
2431    new_body: syn::Expr,
2432    modified: bool,
2433    current_function: Option<String>,
2434    modified_function: Option<String>,
2435}
2436
2437impl VisitMut for MatchArmUpdater {
2438    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2439        let prev_fn = self.current_function.clone();
2440        self.current_function = Some(node.sig.ident.to_string());
2441
2442        syn::visit_mut::visit_item_fn_mut(self, node);
2443
2444        self.current_function = prev_fn;
2445    }
2446
2447    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2448        // Check if we're in the right function (if specified)
2449        if let Some(ref target) = self.target_function {
2450            if self.current_function.as_ref() != Some(target) {
2451                syn::visit_mut::visit_expr_match_mut(self, node);
2452                return;
2453            }
2454        }
2455
2456        // Find and update the matching arm
2457        for arm in &mut node.arms {
2458            let pattern_str = arm.pat.to_token_stream().to_string();
2459            // Normalize whitespace for comparison
2460            let pattern_normalized = pattern_str.replace(" ", "");
2461            let target_normalized = self.pattern_to_match.replace(" ", "");
2462
2463            if pattern_normalized == target_normalized {
2464                arm.body = Box::new(self.new_body.clone());
2465                self.modified = true;
2466                self.modified_function = self.current_function.clone();
2467                break;
2468            }
2469        }
2470
2471        syn::visit_mut::visit_expr_match_mut(self, node);
2472    }
2473}
2474
2475// Visitor for removing match arms
2476struct MatchArmRemover {
2477    target_function: Option<String>,
2478    pattern_to_remove: String,
2479    modified: bool,
2480    current_function: Option<String>,
2481    modified_function: Option<String>,
2482}
2483
2484impl VisitMut for MatchArmRemover {
2485    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2486        let prev_fn = self.current_function.clone();
2487        self.current_function = Some(node.sig.ident.to_string());
2488
2489        syn::visit_mut::visit_item_fn_mut(self, node);
2490
2491        self.current_function = prev_fn;
2492    }
2493
2494    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2495        // Check if we're in the right function (if specified)
2496        if let Some(ref target) = self.target_function {
2497            if self.current_function.as_ref() != Some(target) {
2498                syn::visit_mut::visit_expr_match_mut(self, node);
2499                return;
2500            }
2501        }
2502
2503        // Find and remove the matching arm
2504        let mut index_to_remove = None;
2505        for (i, arm) in node.arms.iter().enumerate() {
2506            let pattern_str = arm.pat.to_token_stream().to_string();
2507            // Normalize whitespace for comparison
2508            let pattern_normalized = pattern_str.replace(" ", "");
2509            let target_normalized = self.pattern_to_remove.replace(" ", "");
2510
2511            if pattern_normalized == target_normalized {
2512                index_to_remove = Some(i);
2513                break;
2514            }
2515        }
2516
2517        if let Some(index) = index_to_remove {
2518            node.arms.remove(index);
2519            self.modified = true;
2520            self.modified_function = self.current_function.clone();
2521        }
2522
2523        syn::visit_mut::visit_expr_match_mut(self, node);
2524    }
2525}
2526
2527// Visitor for adding multiple match arms at once (for auto-detect)
2528struct MultiMatchArmAdder {
2529    target_function: Option<String>,
2530    arms_to_add: Vec<(String, Arm)>,  // (pattern_string, arm)
2531    modified: bool,
2532    current_function: Option<String>,
2533    modified_function: Option<String>,
2534}
2535
2536impl VisitMut for MultiMatchArmAdder {
2537    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2538        let prev_fn = self.current_function.clone();
2539        self.current_function = Some(node.sig.ident.to_string());
2540
2541        syn::visit_mut::visit_item_fn_mut(self, node);
2542
2543        self.current_function = prev_fn;
2544    }
2545
2546    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2547        // Check if we're in the right function (if specified)
2548        if let Some(ref target) = self.target_function {
2549            if self.current_function.as_ref() != Some(target) {
2550                syn::visit_mut::visit_expr_match_mut(self, node);
2551                return;
2552            }
2553        }
2554
2555        // Add all missing arms
2556        for (pattern_str, arm) in &self.arms_to_add {
2557            // Check if the pattern already exists (idempotent)
2558            let already_exists = node.arms.iter().any(|existing_arm| {
2559                existing_arm.pat.to_token_stream().to_string() == *pattern_str
2560            });
2561
2562            if !already_exists {
2563                node.arms.push(arm.clone());
2564                self.modified = true;
2565                self.modified_function = self.current_function.clone();
2566            }
2567        }
2568
2569        syn::visit_mut::visit_expr_match_mut(self, node);
2570    }
2571}
2572
2573// Visitor for adding fields to struct literal expressions
2574struct StructLiteralFieldAdder {
2575    struct_name: String,
2576    field_def: String,
2577    field_name: String,
2578    position: InsertPosition,
2579    modified: bool,
2580}
2581
2582impl VisitMut for StructLiteralFieldAdder {
2583    fn visit_expr_mut(&mut self, node: &mut Expr) {
2584        // Check if this is a struct literal expression
2585        if let Expr::Struct(expr_struct) = node {
2586            // Get the struct name (last segment of the path)
2587            let struct_name = expr_struct.path.segments.last()
2588                .map(|seg| seg.ident.to_string());
2589
2590            if struct_name.as_ref() == Some(&self.struct_name) {
2591                // Check if field already exists (idempotent)
2592                let field_exists = expr_struct.fields.iter().any(|fv| {
2593                    fv.member.to_token_stream().to_string() == self.field_name
2594                });
2595
2596                if !field_exists {
2597                    // Parse the field value from field_def
2598                    // field_def is like "return_type: None"
2599                    let field_value_code = format!("{{ {} }}", self.field_def);
2600                    if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
2601                        if let Some(new_fv) = expr.fields.first() {
2602                            // Determine where to insert
2603                            match &self.position {
2604                                InsertPosition::First => {
2605                                    expr_struct.fields.insert(0, new_fv.clone());
2606                                    self.modified = true;
2607                                }
2608                                InsertPosition::Last => {
2609                                    expr_struct.fields.push(new_fv.clone());
2610                                    self.modified = true;
2611                                }
2612                                InsertPosition::After(after_field) => {
2613                                    // Find the position of the field to insert after
2614                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2615                                        fv.member.to_token_stream().to_string() == *after_field
2616                                    }) {
2617                                        expr_struct.fields.insert(pos + 1, new_fv.clone());
2618                                        self.modified = true;
2619                                    }
2620                                }
2621                                InsertPosition::Before(before_field) => {
2622                                    // Find the position of the field to insert before
2623                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2624                                        fv.member.to_token_stream().to_string() == *before_field
2625                                    }) {
2626                                        expr_struct.fields.insert(pos, new_fv.clone());
2627                                        self.modified = true;
2628                                    }
2629                                }
2630                            }
2631                        }
2632                    }
2633                }
2634            }
2635        }
2636
2637        // IMPORTANT: Visit children AFTER processing this node
2638        // This ensures we traverse into nested expressions
2639        syn::visit_mut::visit_expr_mut(self, node);
2640    }
2641}