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>, }
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 fn format_field(field: &Field) -> String {
35 let mut result = String::new();
36
37 if let syn::Visibility::Public(_) = field.vis {
39 result.push_str("pub ");
40 }
41
42 if let Some(ident) = &field.ident {
44 result.push_str(&ident.to_string());
45 }
46
47 result.push_str(": ");
49
50 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 let mut modified_nodes = Vec::new();
89
90 let item_struct = self.syntax_tree.items.iter()
92 .find_map(|item| {
93 if let Item::Struct(s) = item {
94 if s.ident == op.struct_name {
95 return Some(s.clone());
96 }
97 }
98 None
99 })
100 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
101
102 if let Some(ref where_filter) = op.where_filter {
104 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
105 return Ok(ModificationResult {
107 changed: false,
108 modified_nodes: vec![],
109 });
110 }
111 }
112
113 if op.literal_default.is_none() {
115 let backup_node = BackupNode {
117 node_type: "ItemStruct".to_string(),
118 identifier: op.struct_name.clone(),
119 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
120 location: self.span_to_location(item_struct.span()),
121 };
122
123 let modified = self.insert_struct_field(&item_struct, op)
125 .context("Failed to add field to struct definition")?;
126
127 if !modified {
128 return Ok(ModificationResult {
129 changed: false,
130 modified_nodes: vec![],
131 });
132 }
133
134 return Ok(ModificationResult {
135 changed: true,
136 modified_nodes: vec![backup_node],
137 });
138 }
139
140 let literal_default = op.literal_default.as_ref().unwrap();
144
145 let has_type = op.field_def.contains(':');
148
149 let mut def_modified = false;
150 if has_type {
151 let backup_node = BackupNode {
153 node_type: "ItemStruct".to_string(),
154 identifier: op.struct_name.clone(),
155 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
156 location: self.span_to_location(item_struct.span()),
157 };
158
159 def_modified = self.insert_struct_field(&item_struct, op)
161 .context("Failed to add field to struct definition")?;
162
163 if def_modified {
164 modified_nodes.push(backup_node);
165 self.syntax_tree = syn::parse_str(&self.content)
167 .context("Failed to re-parse content after adding struct field")?;
168 self.line_offsets = Self::compute_line_offsets(&self.content);
169 }
170 }
171
172 let field_name = op.field_def.split(':')
175 .next()
176 .map(|s| s.trim().to_string())
177 .context("Failed to extract field name from field definition")?;
178
179 let literal_op = AddStructLiteralFieldOp {
181 struct_name: op.struct_name.clone(),
182 field_def: format!("{}: {}", field_name, literal_default),
183 position: op.position.clone(),
184 };
185
186 let literal_result = self.add_struct_literal_field(&literal_op)
188 .context("Failed to update struct literals")?;
189 modified_nodes.extend(literal_result.modified_nodes);
190
191 Ok(ModificationResult {
192 changed: true,
193 modified_nodes,
194 })
195 }
196
197 fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
198 if let Fields::Named(ref fields) = item_struct.fields {
199 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
201 let dummy: ItemStruct = parse_str(&field_code)
202 .context("Failed to parse field definition")?;
203
204 let new_field = if let Fields::Named(ref nf) = dummy.fields {
205 nf.named.first()
206 .context("No field found in definition")?
207 .clone()
208 } else {
209 anyhow::bail!("Expected named field");
210 };
211
212 let new_field_name = new_field.ident.as_ref()
214 .map(|i| i.to_string())
215 .context("Field must have a name")?;
216
217 if fields.named.iter().any(|f| {
218 f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
219 }) {
220 return Ok(false);
222 }
223
224 let insert_pos = match &op.position {
226 InsertPosition::First => {
227 if let Some(first_field) = fields.named.first() {
228 self.span_to_byte_offset(first_field.span().start())
229 } else {
230 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
232 brace_pos + 1
233 }
234 }
235 InsertPosition::Last => {
236 if let Some(last_field) = fields.named.last() {
237 let end = self.span_to_byte_offset(last_field.span().end());
238 self.find_after_field_end(end)
240 } else {
241 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
243 brace_pos + 1
244 }
245 }
246 InsertPosition::After(name) => {
247 let field = fields.named.iter()
248 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
249 .with_context(|| format!("Field '{}' not found", name))?;
250 let end = self.span_to_byte_offset(field.span().end());
251 self.find_after_field_end(end)
252 }
253 InsertPosition::Before(name) => {
254 let field = fields.named.iter()
255 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
256 .with_context(|| format!("Field '{}' not found", name))?;
257 self.span_to_byte_offset(field.span().start())
258 }
259 };
260
261 let indent = self.get_indentation(insert_pos);
263 let field_str = Self::format_field(&new_field);
264 let insert_text = if matches!(op.position, InsertPosition::First) {
265 format!("\n{}{},", indent, field_str)
266 } else {
267 format!("\n{}{},", indent, field_str)
268 };
269
270 self.content.insert_str(insert_pos, &insert_text);
271 return Ok(true);
272 }
273
274 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
275 }
276
277 pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
278 let item_struct = self.syntax_tree.items.iter()
280 .find_map(|item| {
281 if let Item::Struct(s) = item {
282 if s.ident == op.struct_name {
283 return Some(s.clone());
284 }
285 }
286 None
287 })
288 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
289
290 if let Some(ref where_filter) = op.where_filter {
292 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
293 return Ok(ModificationResult {
295 changed: false,
296 modified_nodes: vec![],
297 });
298 }
299 }
300
301 let backup_node = BackupNode {
303 node_type: "ItemStruct".to_string(),
304 identifier: op.struct_name.clone(),
305 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
306 location: self.span_to_location(item_struct.span()),
307 };
308
309 let modified = self.replace_struct_field(&item_struct, op)?;
310
311 Ok(ModificationResult {
312 changed: modified,
313 modified_nodes: if modified { vec![backup_node] } else { vec![] },
314 })
315 }
316
317 fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
318 if let Fields::Named(ref fields) = item_struct.fields {
319 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
321 let dummy: ItemStruct = parse_str(&field_code)
322 .context("Failed to parse field definition")?;
323
324 let new_field = if let Fields::Named(ref nf) = dummy.fields {
325 nf.named.first()
326 .context("No field found in definition")?
327 .clone()
328 } else {
329 anyhow::bail!("Expected named field");
330 };
331
332 let field_name = new_field.ident.as_ref()
334 .map(|i| i.to_string())
335 .context("Field must have a name")?;
336
337 let existing_field = fields.named.iter()
339 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
340 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
341
342 let start = self.span_to_byte_offset(existing_field.span().start());
344 let end = self.span_to_byte_offset(existing_field.span().end());
345
346 let new_field_str = Self::format_field(&new_field);
348
349 self.content.replace_range(start..end, &new_field_str);
351
352 return Ok(true);
353 }
354
355 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
356 }
357
358 pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
359 let item_struct = self.syntax_tree.items.iter()
361 .find_map(|item| {
362 if let Item::Struct(s) = item {
363 if s.ident == op.struct_name {
364 return Some(s.clone());
365 }
366 }
367 None
368 })
369 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
370
371 if let Some(ref where_filter) = op.where_filter {
373 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
374 return Ok(ModificationResult {
376 changed: false,
377 modified_nodes: vec![],
378 });
379 }
380 }
381
382 let backup_node = BackupNode {
384 node_type: "ItemStruct".to_string(),
385 identifier: op.struct_name.clone(),
386 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
387 location: self.span_to_location(item_struct.span()),
388 };
389
390 if let Fields::Named(ref fields) = item_struct.fields {
391 let field_to_remove = fields.named.iter()
393 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
394 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
395
396 let start = self.span_to_byte_offset(field_to_remove.span().start());
398 let mut end = self.span_to_byte_offset(field_to_remove.span().end());
399
400 while end < self.content.len() {
402 match self.content.as_bytes()[end] as char {
403 ',' => {
404 end += 1;
405 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
407 end += 1;
408 }
409 break;
410 }
411 ' ' | '\t' => end += 1,
412 '\n' => {
413 end += 1;
414 break;
415 }
416 _ => break,
417 }
418 }
419
420 let mut line_start = start;
422 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
423 line_start -= 1;
424 }
425
426 let before_field = &self.content[line_start..start];
428 if before_field.trim().is_empty() {
429 self.content.replace_range(line_start..end, "");
431 } else {
432 self.content.replace_range(start..end, "");
434 }
435
436 return Ok(ModificationResult {
437 changed: true,
438 modified_nodes: vec![backup_node],
439 });
440 }
441
442 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
443 }
444
445 pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
446 let field_name = op.field_def.split(':')
448 .next()
449 .map(|s| s.trim().to_string())
450 .context("Field definition must contain ':'")?;
451
452 let backup_nodes = self.collect_struct_literal_backups(&op.struct_name);
454
455 let mut visitor = StructLiteralFieldAdder {
457 struct_name: op.struct_name.clone(),
458 field_def: op.field_def.clone(),
459 field_name,
460 position: op.position.clone(),
461 modified: false,
462 };
463
464 visitor.visit_file_mut(&mut self.syntax_tree);
465
466 if visitor.modified {
467 self.content = prettyplease::unparse(&self.syntax_tree);
469 Ok(ModificationResult {
470 changed: true,
471 modified_nodes: backup_nodes,
472 })
473 } else {
474 Ok(ModificationResult {
475 changed: false,
476 modified_nodes: vec![],
477 })
478 }
479 }
480
481 fn collect_struct_literal_backups(&self, struct_name: &str) -> Vec<BackupNode> {
483 use syn::visit::Visit;
484
485 struct LiteralCollector {
486 struct_name: String,
487 backups: Vec<BackupNode>,
488 counter: usize,
489 }
490
491 impl<'ast> Visit<'ast> for LiteralCollector {
492 fn visit_expr(&mut self, node: &'ast Expr) {
493 if let Expr::Struct(expr_struct) = node {
494 let matches = if self.struct_name.contains("::") {
500 if self.struct_name.starts_with("*::") {
502 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
505 .map(|seg| seg.ident.to_string() == target_name)
506 .unwrap_or(false)
507 } else {
508 let path_str = expr_struct.path.segments.iter()
510 .map(|seg| seg.ident.to_string())
511 .collect::<Vec<_>>()
512 .join("::");
513 path_str == self.struct_name
514 }
515 } else {
516 expr_struct.path.segments.len() == 1
518 && expr_struct.path.segments.last()
519 .map(|seg| seg.ident.to_string() == self.struct_name)
520 .unwrap_or(false)
521 };
522
523 if matches {
524 self.backups.push(BackupNode {
525 node_type: "ExprStruct".to_string(),
526 identifier: format!("{}#{}", self.struct_name, self.counter),
527 original_content: expr_struct.to_token_stream().to_string(),
528 location: NodeLocation {
529 line: 0, column: 0,
531 end_line: 0,
532 end_column: 0,
533 },
534 });
535 self.counter += 1;
536 }
537 }
538 syn::visit::visit_expr(self, node);
539 }
540 }
541
542 let mut collector = LiteralCollector {
543 struct_name: struct_name.to_string(),
544 backups: Vec::new(),
545 counter: 0,
546 };
547
548 collector.visit_file(&self.syntax_tree);
549 collector.backups
550 }
551
552 pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
553 let item_enum = self.syntax_tree.items.iter()
555 .find_map(|item| {
556 if let Item::Enum(e) = item {
557 if e.ident == op.enum_name {
558 return Some(e.clone());
559 }
560 }
561 None
562 })
563 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
564
565 if let Some(ref where_filter) = op.where_filter {
567 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
568 return Ok(ModificationResult {
570 changed: false,
571 modified_nodes: vec![],
572 });
573 }
574 }
575
576 let backup_node = BackupNode {
578 node_type: "ItemEnum".to_string(),
579 identifier: op.enum_name.clone(),
580 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
581 location: self.span_to_location(item_enum.span()),
582 };
583
584 let modified = self.insert_enum_variant(&item_enum, op)?;
585
586 Ok(ModificationResult {
587 changed: modified,
588 modified_nodes: if modified { vec![backup_node] } else { vec![] },
589 })
590 }
591
592 fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
593 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
595 let dummy: ItemEnum = parse_str(&variant_code)
596 .context("Failed to parse variant definition")?;
597
598 let new_variant = dummy.variants.first()
599 .context("No variant found in definition")?
600 .clone();
601
602 let variant_name = new_variant.ident.to_string();
604 if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
605 return Ok(false);
607 }
608
609 let insert_pos = match &op.position {
611 InsertPosition::First => {
612 if let Some(first_var) = item_enum.variants.first() {
613 self.span_to_byte_offset(first_var.span().start())
614 } else {
615 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
616 brace_pos + 1
617 }
618 }
619 InsertPosition::Last => {
620 if let Some(last_var) = item_enum.variants.last() {
621 let end = self.span_to_byte_offset(last_var.span().end());
622 self.find_after_field_end(end)
623 } else {
624 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
625 brace_pos + 1
626 }
627 }
628 InsertPosition::After(name) => {
629 let variant = item_enum.variants.iter()
630 .find(|v| v.ident.to_string() == *name)
631 .with_context(|| format!("Variant '{}' not found", name))?;
632 let end = self.span_to_byte_offset(variant.span().end());
633 self.find_after_field_end(end)
634 }
635 InsertPosition::Before(name) => {
636 let variant = item_enum.variants.iter()
637 .find(|v| v.ident.to_string() == *name)
638 .with_context(|| format!("Variant '{}' not found", name))?;
639 self.span_to_byte_offset(variant.span().start())
640 }
641 };
642
643 let indent = self.get_indentation(insert_pos);
644 let variant_str = new_variant.to_token_stream().to_string();
645 let insert_text = format!("\n{}{},", indent, variant_str);
646
647 self.content.insert_str(insert_pos, &insert_text);
648 Ok(true)
649 }
650
651 fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
652 let item_enum = self.syntax_tree.items.iter()
654 .find_map(|item| {
655 if let Item::Enum(e) = item {
656 if e.ident == op.enum_name {
657 return Some(e.clone());
658 }
659 }
660 None
661 })
662 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
663
664 if let Some(ref where_filter) = op.where_filter {
666 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
667 return Ok(ModificationResult {
669 changed: false,
670 modified_nodes: vec![],
671 });
672 }
673 }
674
675 let backup_node = BackupNode {
677 node_type: "ItemEnum".to_string(),
678 identifier: op.enum_name.clone(),
679 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
680 location: self.span_to_location(item_enum.span()),
681 };
682
683 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
685 let dummy: ItemEnum = parse_str(&variant_code)
686 .context("Failed to parse variant definition")?;
687
688 let new_variant = dummy.variants.first()
689 .context("No variant found in definition")?
690 .clone();
691
692 let variant_name = new_variant.ident.to_string();
693
694 let existing_variant = item_enum.variants.iter()
696 .find(|v| v.ident.to_string() == variant_name)
697 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
698
699 let start = self.span_to_byte_offset(existing_variant.span().start());
701 let end = self.span_to_byte_offset(existing_variant.span().end());
702
703 let variant_str = new_variant.to_token_stream().to_string();
705 self.content.replace_range(start..end, &variant_str);
706
707 Ok(ModificationResult {
708 changed: true,
709 modified_nodes: vec![backup_node],
710 })
711 }
712
713 pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
714 let item_enum = self.syntax_tree.items.iter()
716 .find_map(|item| {
717 if let Item::Enum(e) = item {
718 if e.ident == op.enum_name {
719 return Some(e.clone());
720 }
721 }
722 None
723 })
724 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
725
726 if let Some(ref where_filter) = op.where_filter {
728 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
729 return Ok(ModificationResult {
731 changed: false,
732 modified_nodes: vec![],
733 });
734 }
735 }
736
737 let backup_node = BackupNode {
739 node_type: "ItemEnum".to_string(),
740 identifier: op.enum_name.clone(),
741 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
742 location: self.span_to_location(item_enum.span()),
743 };
744
745 let variant_to_remove = item_enum.variants.iter()
747 .find(|v| v.ident.to_string() == op.variant_name)
748 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
749
750 let start = self.span_to_byte_offset(variant_to_remove.span().start());
752 let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
753
754 while end < self.content.len() {
756 match self.content.as_bytes()[end] as char {
757 ',' => {
758 end += 1;
759 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
760 end += 1;
761 }
762 break;
763 }
764 ' ' | '\t' => end += 1,
765 '\n' => {
766 end += 1;
767 break;
768 }
769 _ => break,
770 }
771 }
772
773 let mut line_start = start;
775 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
776 line_start -= 1;
777 }
778
779 let before_variant = &self.content[line_start..start];
780 if before_variant.trim().is_empty() {
781 self.content.replace_range(line_start..end, "");
782 } else {
783 self.content.replace_range(start..end, "");
784 }
785
786 Ok(ModificationResult {
787 changed: true,
788 modified_nodes: vec![backup_node],
789 })
790 }
791
792 pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
793 if op.auto_detect {
794 self.add_missing_match_arms(op)
796 } else {
797 self.add_single_match_arm(op)
799 }
800 }
801
802 fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
803 let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
805 let expr: syn::Expr = parse_str(&dummy_match)
806 .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
807
808 let arm = if let syn::Expr::Match(match_expr) = expr {
810 match_expr.arms.into_iter().next()
811 .context("Failed to extract arm from dummy match")?
812 } else {
813 anyhow::bail!("Expected match expression");
814 };
815
816 let backup_node = if let Some(ref fn_name) = op.function_name {
818 self.get_function_backup(fn_name)?
819 } else {
820 BackupNode {
823 node_type: "Unknown".to_string(),
824 identifier: "match_expression".to_string(),
825 original_content: String::new(),
826 location: NodeLocation {
827 line: 0,
828 column: 0,
829 end_line: 0,
830 end_column: 0,
831 },
832 }
833 };
834
835 let mut visitor = MatchArmAdder {
837 target_function: op.function_name.clone(),
838 arm_to_add: arm,
839 modified: false,
840 current_function: None,
841 modified_function: None,
842 };
843
844 visitor.visit_file_mut(&mut self.syntax_tree);
845
846 if visitor.modified {
847 self.replace_modified_functions(&visitor.modified_function)?;
849 Ok(ModificationResult {
850 changed: true,
851 modified_nodes: vec![backup_node],
852 })
853 } else {
854 Ok(ModificationResult {
855 changed: false,
856 modified_nodes: vec![],
857 })
858 }
859 }
860
861 fn unparse_item(&self, item: &Item) -> String {
863 let temp_file = syn::File {
864 shebang: None,
865 attrs: Vec::new(),
866 items: vec![item.clone()],
867 };
868 prettyplease::unparse(&temp_file).trim().to_string()
869 }
870
871 fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
873 for item in &self.syntax_tree.items {
874 if let Item::Fn(f) = item {
875 if f.sig.ident == fn_name {
876 return Ok(BackupNode {
877 node_type: "ItemFn".to_string(),
878 identifier: fn_name.to_string(),
879 original_content: self.unparse_item(&Item::Fn(f.clone())),
880 location: self.span_to_location(f.span()),
881 });
882 }
883 }
884 }
885 anyhow::bail!("Function '{}' not found", fn_name)
886 }
887
888 fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
889 let enum_name = op.enum_name.as_ref()
891 .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
892
893 let enum_variants = self.find_enum_variants(enum_name)?;
895
896 if enum_variants.is_empty() {
897 anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
898 }
899
900 let existing_patterns = self.find_existing_match_patterns(&op.function_name);
902
903 let mut missing_variants = Vec::new();
905 for variant in &enum_variants {
906 let pattern = format!("{}::{}", enum_name, variant);
907 let pattern_normalized = pattern.replace(" ", "");
908
909 let exists = existing_patterns.iter().any(|p| {
910 p.replace(" ", "") == pattern_normalized
911 });
912
913 if !exists {
914 missing_variants.push(variant.clone());
915 }
916 }
917
918 if missing_variants.is_empty() {
919 println!("All enum variants already covered in match expressions");
920 return Ok(ModificationResult {
921 changed: false,
922 modified_nodes: vec![],
923 });
924 }
925
926 let backup_node = if let Some(ref fn_name) = op.function_name {
928 self.get_function_backup(fn_name)?
929 } else {
930 BackupNode {
931 node_type: "Unknown".to_string(),
932 identifier: "match_expression".to_string(),
933 original_content: String::new(),
934 location: NodeLocation {
935 line: 0,
936 column: 0,
937 end_line: 0,
938 end_column: 0,
939 },
940 }
941 };
942
943 let mut arms_to_add = Vec::new();
945 for variant in &missing_variants {
946 let pattern = format!("{}::{}", enum_name, variant);
947 let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
948 let expr: syn::Expr = parse_str(&dummy_match)
949 .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
950
951 if let syn::Expr::Match(match_expr) = expr {
952 if let Some(arm) = match_expr.arms.into_iter().next() {
953 arms_to_add.push((pattern.clone(), arm));
954 }
955 }
956 }
957
958 let mut visitor = MultiMatchArmAdder {
960 target_function: op.function_name.clone(),
961 arms_to_add,
962 modified: false,
963 current_function: None,
964 modified_function: None,
965 };
966
967 visitor.visit_file_mut(&mut self.syntax_tree);
968
969 if visitor.modified {
970 for variant in &missing_variants {
972 println!("Added match arm for: {}::{}", enum_name, variant);
973 }
974
975 self.replace_modified_functions(&visitor.modified_function)?;
977 Ok(ModificationResult {
978 changed: true,
979 modified_nodes: vec![backup_node],
980 })
981 } else {
982 Ok(ModificationResult {
983 changed: false,
984 modified_nodes: vec![],
985 })
986 }
987 }
988
989 fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
990 for item in &self.syntax_tree.items {
992 if let Item::Enum(e) = item {
993 if e.ident == enum_name {
994 let variants: Vec<String> = e.variants.iter()
995 .map(|v| v.ident.to_string())
996 .collect();
997 return Ok(variants);
998 }
999 }
1000 }
1001
1002 Ok(Vec::new())
1003 }
1004
1005 fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1006 use syn::visit::Visit;
1007
1008 struct PatternCollector {
1009 target_function: Option<String>,
1010 current_function: Option<String>,
1011 patterns: Vec<String>,
1012 }
1013
1014 impl<'ast> Visit<'ast> for PatternCollector {
1015 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1016 let prev_fn = self.current_function.clone();
1017 self.current_function = Some(node.sig.ident.to_string());
1018 syn::visit::visit_item_fn(self, node);
1019 self.current_function = prev_fn;
1020 }
1021
1022 fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1023 if let Some(ref target) = self.target_function {
1025 if self.current_function.as_ref() != Some(target) {
1026 syn::visit::visit_expr_match(self, node);
1027 return;
1028 }
1029 }
1030
1031 for arm in &node.arms {
1033 self.patterns.push(arm.pat.to_token_stream().to_string());
1034 }
1035
1036 syn::visit::visit_expr_match(self, node);
1037 }
1038 }
1039
1040 let mut collector = PatternCollector {
1041 target_function: function_name.clone(),
1042 current_function: None,
1043 patterns: Vec::new(),
1044 };
1045
1046 collector.visit_file(&self.syntax_tree);
1047 collector.patterns
1048 }
1049
1050 pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1051 let backup_node = if let Some(ref fn_name) = op.function_name {
1053 self.get_function_backup(fn_name)?
1054 } else {
1055 BackupNode {
1056 node_type: "Unknown".to_string(),
1057 identifier: "match_expression".to_string(),
1058 original_content: String::new(),
1059 location: NodeLocation {
1060 line: 0,
1061 column: 0,
1062 end_line: 0,
1063 end_column: 0,
1064 },
1065 }
1066 };
1067
1068 let new_body: syn::Expr = parse_str(&op.new_body)
1070 .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1071
1072 let mut visitor = MatchArmUpdater {
1074 target_function: op.function_name.clone(),
1075 pattern_to_match: op.pattern.clone(),
1076 new_body,
1077 modified: false,
1078 current_function: None,
1079 modified_function: None,
1080 };
1081
1082 visitor.visit_file_mut(&mut self.syntax_tree);
1083
1084 if visitor.modified {
1085 self.replace_modified_functions(&visitor.modified_function)?;
1087 Ok(ModificationResult {
1088 changed: true,
1089 modified_nodes: vec![backup_node],
1090 })
1091 } else {
1092 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1093 }
1094 }
1095
1096 pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1097 let backup_node = if let Some(ref fn_name) = op.function_name {
1099 self.get_function_backup(fn_name)?
1100 } else {
1101 BackupNode {
1102 node_type: "Unknown".to_string(),
1103 identifier: "match_expression".to_string(),
1104 original_content: String::new(),
1105 location: NodeLocation {
1106 line: 0,
1107 column: 0,
1108 end_line: 0,
1109 end_column: 0,
1110 },
1111 }
1112 };
1113
1114 let mut visitor = MatchArmRemover {
1116 target_function: op.function_name.clone(),
1117 pattern_to_remove: op.pattern.clone(),
1118 modified: false,
1119 current_function: None,
1120 modified_function: None,
1121 };
1122
1123 visitor.visit_file_mut(&mut self.syntax_tree);
1124
1125 if visitor.modified {
1126 self.replace_modified_functions(&visitor.modified_function)?;
1128 Ok(ModificationResult {
1129 changed: true,
1130 modified_nodes: vec![backup_node],
1131 })
1132 } else {
1133 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1134 }
1135 }
1136
1137 pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1138 let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1140 let dummy: syn::ItemImpl = parse_str(&method_code)
1141 .context("Failed to parse method definition")?;
1142
1143 let new_method = dummy.items.first()
1144 .context("No method found in definition")?
1145 .clone();
1146
1147 let method_name = match &new_method {
1149 syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1150 _ => anyhow::bail!("Only method definitions are supported"),
1151 };
1152
1153 let impl_index = self.syntax_tree.items.iter().position(|item| {
1155 if let Item::Impl(impl_block) = item {
1156 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1158 if let Some(segment) = type_path.path.segments.last() {
1159 return segment.ident == op.target;
1160 }
1161 }
1162 }
1163 false
1164 }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1165
1166 let impl_block = match &self.syntax_tree.items[impl_index] {
1168 Item::Impl(i) => i,
1169 _ => unreachable!(),
1170 };
1171
1172 let method_exists = impl_block.items.iter().any(|item| {
1173 if let syn::ImplItem::Fn(f) = item {
1174 f.sig.ident == method_name
1175 } else {
1176 false
1177 }
1178 });
1179
1180 if method_exists {
1181 return Ok(ModificationResult {
1182 changed: false,
1183 modified_nodes: vec![],
1184 });
1185 }
1186
1187 let backup_node = BackupNode {
1189 node_type: "ItemImpl".to_string(),
1190 identifier: op.target.clone(),
1191 original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1192 location: self.span_to_location(impl_block.span()),
1193 };
1194
1195 let impl_span = impl_block.span();
1197
1198 match &mut self.syntax_tree.items[impl_index] {
1200 Item::Impl(impl_block) => {
1201 match &op.position {
1203 InsertPosition::First => {
1204 impl_block.items.insert(0, new_method);
1205 }
1206 InsertPosition::Last => {
1207 impl_block.items.push(new_method);
1208 }
1209 InsertPosition::After(name) => {
1210 let pos = impl_block.items.iter().position(|item| {
1211 if let syn::ImplItem::Fn(f) = item {
1212 f.sig.ident == name
1213 } else {
1214 false
1215 }
1216 }).with_context(|| format!("Method '{}' not found", name))?;
1217 impl_block.items.insert(pos + 1, new_method);
1218 }
1219 InsertPosition::Before(name) => {
1220 let pos = impl_block.items.iter().position(|item| {
1221 if let syn::ImplItem::Fn(f) = item {
1222 f.sig.ident == name
1223 } else {
1224 false
1225 }
1226 }).with_context(|| format!("Method '{}' not found", name))?;
1227 impl_block.items.insert(pos, new_method);
1228 }
1229 }
1230 }
1231 _ => unreachable!(),
1232 }
1233
1234 self.replace_formatted_item(impl_index, impl_span)?;
1236
1237 Ok(ModificationResult {
1238 changed: true,
1239 modified_nodes: vec![backup_node],
1240 })
1241 }
1242
1243 pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1244 let use_code = format!("use {};", op.use_path);
1246 let use_item: syn::ItemUse = parse_str(&use_code)
1247 .context("Failed to parse use statement")?;
1248
1249 let use_exists = self.syntax_tree.items.iter().any(|item| {
1251 if let Item::Use(existing_use) = item {
1252 existing_use.tree.to_token_stream().to_string() ==
1254 use_item.tree.to_token_stream().to_string()
1255 } else {
1256 false
1257 }
1258 });
1259
1260 if use_exists {
1261 return Ok(ModificationResult {
1262 changed: false,
1263 modified_nodes: vec![],
1264 });
1265 }
1266
1267 let backup_node = BackupNode {
1269 node_type: "ItemUse".to_string(),
1270 identifier: op.use_path.clone(),
1271 original_content: format!("use {};", op.use_path),
1272 location: NodeLocation {
1273 line: 0,
1274 column: 0,
1275 end_line: 0,
1276 end_column: 0,
1277 },
1278 };
1279
1280 let insert_index = match &op.position {
1282 InsertPosition::First => 0,
1283 InsertPosition::Last => {
1284 self.syntax_tree.items.iter()
1286 .rposition(|item| matches!(item, Item::Use(_)))
1287 .map(|i| i + 1)
1288 .unwrap_or(0)
1289 }
1290 InsertPosition::After(path) => {
1291 let pos = self.syntax_tree.items.iter().position(|item| {
1293 if let Item::Use(u) = item {
1294 u.tree.to_token_stream().to_string().contains(path)
1295 } else {
1296 false
1297 }
1298 }).with_context(|| format!("Use statement for '{}' not found", path))?;
1299 pos + 1
1300 }
1301 InsertPosition::Before(path) => {
1302 self.syntax_tree.items.iter().position(|item| {
1304 if let Item::Use(u) = item {
1305 u.tree.to_token_stream().to_string().contains(path)
1306 } else {
1307 false
1308 }
1309 }).with_context(|| format!("Use statement for '{}' not found", path))?
1310 }
1311 };
1312
1313 self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1315
1316 let insert_line_pos = if insert_index == 0 {
1319 0
1321 } else {
1322 let prev_item = &self.syntax_tree.items[insert_index - 1];
1324 let span = prev_item.span();
1325 let end_pos = self.span_to_byte_offset(span.end());
1326
1327 let mut line_end = end_pos;
1329 while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1330 line_end += 1;
1331 }
1332 if line_end < self.content.len() {
1334 line_end + 1
1335 } else {
1336 self.content.push('\n');
1338 self.content.len()
1339 }
1340 };
1341
1342 let use_str = format!("use {};\n", op.use_path);
1344
1345 self.content.insert_str(insert_line_pos, &use_str);
1347
1348 Ok(ModificationResult {
1349 changed: true,
1350 modified_nodes: vec![backup_node],
1351 })
1352 }
1353
1354 pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1355 let item_index = self.syntax_tree.items.iter().position(|item| {
1357 match (&op.target_type as &str, item) {
1358 ("struct", Item::Struct(s)) => s.ident == op.target_name,
1359 ("enum", Item::Enum(e)) => e.ident == op.target_name,
1360 _ => false,
1361 }
1362 }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1363
1364 let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1366 Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1367 Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1368 _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1369 };
1370
1371 if let Some(ref where_filter) = op.where_filter {
1373 if !self.matches_where_filter(item_attrs, where_filter)? {
1374 return Ok(ModificationResult {
1376 changed: false,
1377 modified_nodes: vec![],
1378 });
1379 }
1380 }
1381
1382 let backup_node = BackupNode {
1384 node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1385 identifier: op.target_name.clone(),
1386 original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1387 location: self.span_to_location(item_span),
1388 };
1389
1390 let new_derives: Vec<String> = op.derives.iter()
1392 .filter(|d| !existing_derives.contains(&d.to_string()))
1393 .cloned()
1394 .collect();
1395
1396 if new_derives.is_empty() {
1397 return Ok(ModificationResult {
1399 changed: false,
1400 modified_nodes: vec![],
1401 });
1402 }
1403
1404 let mut all_derives = existing_derives;
1406 all_derives.extend(new_derives);
1407
1408 let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1410
1411 match &mut self.syntax_tree.items[item_index] {
1413 Item::Struct(s) => {
1414 Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1415 }
1416 Item::Enum(e) => {
1417 Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1418 }
1419 _ => unreachable!(),
1420 }
1421
1422 self.replace_formatted_item(item_index, item_span)?;
1424
1425 Ok(ModificationResult {
1426 changed: true,
1427 modified_nodes: vec![backup_node],
1428 })
1429 }
1430
1431 fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1433 let item_start_pos = self.span_to_byte_offset(original_span.start());
1435 let item_end_pos = self.span_to_byte_offset(original_span.end());
1436
1437 let mut actual_start = item_start_pos;
1439
1440 let mut temp_pos = item_start_pos;
1442 while temp_pos > 0 {
1443 temp_pos = temp_pos.saturating_sub(1);
1445 let mut line_start = temp_pos;
1446 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1447 line_start -= 1;
1448 }
1449
1450 let line = if temp_pos < self.content.len() {
1451 &self.content[line_start..temp_pos + 1]
1452 } else {
1453 &self.content[line_start..]
1454 };
1455 let trimmed = line.trim();
1456
1457 if trimmed.starts_with("#[") {
1458 actual_start = line_start;
1459 temp_pos = line_start;
1460 } else if trimmed.is_empty() {
1461 temp_pos = line_start;
1462 } else {
1463 break;
1464 }
1465
1466 if line_start == 0 {
1467 break;
1468 }
1469 }
1470
1471 let item_clone = self.syntax_tree.items[item_index].clone();
1473 let temp_file = syn::File {
1474 shebang: None,
1475 attrs: Vec::new(),
1476 items: vec![item_clone],
1477 };
1478
1479 let formatted = prettyplease::unparse(&temp_file);
1481 let formatted = formatted.trim();
1482
1483 self.content.replace_range(actual_start..item_end_pos, formatted);
1485
1486 Ok(())
1487 }
1488
1489 fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1491 for attr in attrs {
1492 if attr.path().is_ident("derive") {
1493 if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1494 let tokens_str = meta_list.tokens.to_string();
1495 return tokens_str
1496 .split(',')
1497 .map(|s| s.trim().to_string())
1498 .collect();
1499 }
1500 }
1501 }
1502 Vec::new()
1503 }
1504
1505 fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1510 if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1512 let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1513 let existing_derives = Self::extract_derives(attrs);
1514
1515 for required_trait in required_traits {
1517 if existing_derives.iter().any(|d| d == required_trait) {
1518 return Ok(true);
1519 }
1520 }
1521 return Ok(false);
1522 }
1523
1524 Ok(true)
1526 }
1527
1528 fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1530 let derive_str = derives.join(", ");
1531
1532 let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1534 let parsed: syn::ItemStruct = parse_str(&dummy)
1535 .context("Failed to parse derive attribute")?;
1536
1537 let new_attr = parsed.attrs.into_iter()
1538 .find(|a| a.path().is_ident("derive"))
1539 .context("Failed to extract derive attribute")?;
1540
1541 if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1543 attrs[pos] = new_attr;
1544 } else {
1545 attrs.insert(0, new_attr);
1547 }
1548
1549 Ok(())
1550 }
1551
1552 fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1554 if modified_function.is_none() {
1556 self.content = prettyplease::unparse(&self.syntax_tree);
1557 return Ok(());
1558 }
1559
1560 let original_syntax_tree: File = syn::parse_str(&self.content)
1562 .context("Failed to re-parse original content")?;
1563
1564 let function_name = modified_function.as_ref().unwrap();
1565
1566 let original_fn = original_syntax_tree.items.iter()
1568 .find_map(|item| {
1569 if let Item::Fn(f) = item {
1570 if f.sig.ident == function_name {
1571 return Some(f.clone());
1572 }
1573 }
1574 None
1575 })
1576 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1577
1578 let start = self.span_to_byte_offset(original_fn.span().start());
1580 let end = self.span_to_byte_offset(original_fn.span().end());
1581
1582 let modified_fn = self.syntax_tree.items.iter()
1584 .find_map(|item| {
1585 if let Item::Fn(f) = item {
1586 if f.sig.ident == function_name {
1587 return Some(f.clone());
1588 }
1589 }
1590 None
1591 })
1592 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1593
1594 let dummy_file = syn::File {
1596 shebang: None,
1597 attrs: Vec::new(),
1598 items: vec![Item::Fn(modified_fn)],
1599 };
1600
1601 let formatted_fn = prettyplease::unparse(&dummy_file);
1602
1603 let formatted_fn = formatted_fn.trim();
1605
1606 self.content.replace_range(start..end, formatted_fn);
1608
1609 Ok(())
1610 }
1611
1612 fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1613 let line_idx = pos.line.saturating_sub(1);
1614 if line_idx < self.line_offsets.len() {
1615 self.line_offsets[line_idx] + pos.column
1616 } else {
1617 self.content.len()
1618 }
1619 }
1620
1621 fn find_after_field_end(&self, pos: usize) -> usize {
1622 let mut i = pos;
1624 while i < self.content.len() {
1625 match self.content.as_bytes()[i] as char {
1626 ',' => return i + 1,
1627 '\n' => return i + 1,
1628 _ => i += 1,
1629 }
1630 }
1631 pos
1632 }
1633
1634 fn get_indentation(&self, pos: usize) -> String {
1635 let mut line_start = pos;
1637 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1638 line_start -= 1;
1639 }
1640
1641 let mut indent = String::new();
1643 let mut i = line_start;
1644 while i < self.content.len() {
1645 match self.content.as_bytes()[i] as char {
1646 ' ' | '\t' => {
1647 indent.push(self.content.as_bytes()[i] as char);
1648 i += 1;
1649 }
1650 _ => break,
1651 }
1652 }
1653
1654 if indent.is_empty() {
1656 " ".to_string()
1657 } else {
1658 indent
1659 }
1660 }
1661
1662 pub fn to_string(&self) -> String {
1663 self.content.clone()
1664 }
1665
1666 pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1668 use syn::visit::Visit;
1669 use crate::operations::InspectResult;
1670
1671 let mut results = Vec::new();
1672
1673 match node_type {
1674 "struct-literal" => {
1675 struct StructLiteralVisitor<'a> {
1677 results: &'a mut Vec<InspectResult>,
1678 name_filter: Option<&'a str>,
1679 editor: &'a RustEditor,
1680 }
1681
1682 impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1683 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1684 let filter = match self.name_filter {
1690 Some(f) => f,
1691 None => {
1692 let struct_name = node.path.segments.last()
1694 .map(|seg| seg.ident.to_string())
1695 .unwrap_or_default();
1696
1697 let snippet = self.editor.format_expr_struct(node);
1698 let location = self.editor.span_to_location(node.span());
1699
1700 self.results.push(InspectResult {
1701 file_path: String::new(),
1702 node_type: "ExprStruct".to_string(),
1703 identifier: struct_name,
1704 location,
1705 snippet,
1706 });
1707
1708 syn::visit::visit_expr_struct(self, node);
1709 return;
1710 }
1711 };
1712
1713 let matches = if filter.contains("::") {
1715 if filter.starts_with("*::") {
1717 let target_name = &filter[3..]; node.path.segments.last()
1720 .map(|seg| seg.ident.to_string() == target_name)
1721 .unwrap_or(false)
1722 } else {
1723 let path_str = node.path.segments.iter()
1725 .map(|seg| seg.ident.to_string())
1726 .collect::<Vec<_>>()
1727 .join("::");
1728 path_str == filter
1729 }
1730 } else {
1731 node.path.get_ident()
1733 .map(|ident| ident.to_string() == filter)
1734 .unwrap_or(false)
1735 };
1736
1737 if !matches {
1738 syn::visit::visit_expr_struct(self, node);
1739 return;
1740 }
1741
1742 let struct_name = node.path.segments.last()
1744 .map(|seg| seg.ident.to_string())
1745 .unwrap_or_default();
1746
1747 let snippet = self.editor.format_expr_struct(node);
1749 let location = self.editor.span_to_location(node.span());
1750
1751 self.results.push(InspectResult {
1752 file_path: String::new(), node_type: "ExprStruct".to_string(),
1754 identifier: struct_name,
1755 location,
1756 snippet,
1757 });
1758
1759 syn::visit::visit_expr_struct(self, node);
1761 }
1762 }
1763
1764 let mut visitor = StructLiteralVisitor {
1765 results: &mut results,
1766 name_filter,
1767 editor: self,
1768 };
1769
1770 for item in &self.syntax_tree.items {
1772 syn::visit::visit_item(&mut visitor, item);
1773 }
1774 }
1775 "match-arm" => {
1776 struct MatchArmVisitor<'a> {
1778 results: &'a mut Vec<InspectResult>,
1779 pattern_filter: Option<&'a str>,
1780 editor: &'a RustEditor,
1781 }
1782
1783 impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1784 fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1785 for arm in &node.arms {
1787 let pat = &arm.pat;
1789 let pattern_str = quote::quote!(#pat).to_string();
1790
1791 if let Some(filter) = self.pattern_filter {
1793 let normalized_pattern = pattern_str.replace(" ", "");
1795 let normalized_filter = filter.replace(" ", "");
1796
1797 if !normalized_pattern.contains(&normalized_filter) {
1798 continue;
1799 }
1800 }
1801
1802 let snippet = self.editor.format_match_arm(arm);
1804 let location = self.editor.span_to_location(arm.span());
1805
1806 self.results.push(InspectResult {
1807 file_path: String::new(), node_type: "MatchArm".to_string(),
1809 identifier: pattern_str.replace(" ", ""),
1810 location,
1811 snippet,
1812 });
1813 }
1814
1815 syn::visit::visit_expr_match(self, node);
1817 }
1818 }
1819
1820 let mut visitor = MatchArmVisitor {
1821 results: &mut results,
1822 pattern_filter: name_filter,
1823 editor: self,
1824 };
1825
1826 for item in &self.syntax_tree.items {
1828 syn::visit::visit_item(&mut visitor, item);
1829 }
1830 }
1831 "enum-usage" => {
1832 struct EnumUsageVisitor<'a> {
1834 results: &'a mut Vec<InspectResult>,
1835 path_filter: Option<&'a str>,
1836 editor: &'a RustEditor,
1837 }
1838
1839 impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1840 fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1841 let path = &node.path;
1843 let path_str = quote::quote!(#path).to_string();
1844
1845 if let Some(filter) = self.path_filter {
1847 let normalized_path = path_str.replace(" ", "");
1849 let normalized_filter = filter.replace(" ", "");
1850
1851 if !normalized_path.contains(&normalized_filter) {
1852 syn::visit::visit_expr_path(self, node);
1853 return;
1854 }
1855 }
1856
1857 let snippet = self.editor.format_expr_path(node);
1859 let location = self.editor.span_to_location(node.span());
1860
1861 self.results.push(InspectResult {
1862 file_path: String::new(), node_type: "ExprPath".to_string(),
1864 identifier: path_str.replace(" ", ""),
1865 location,
1866 snippet,
1867 });
1868
1869 syn::visit::visit_expr_path(self, node);
1871 }
1872 }
1873
1874 let mut visitor = EnumUsageVisitor {
1875 results: &mut results,
1876 path_filter: name_filter,
1877 editor: self,
1878 };
1879
1880 for item in &self.syntax_tree.items {
1882 syn::visit::visit_item(&mut visitor, item);
1883 }
1884 }
1885 "function-call" => {
1886 struct FunctionCallVisitor<'a> {
1888 results: &'a mut Vec<InspectResult>,
1889 name_filter: Option<&'a str>,
1890 editor: &'a RustEditor,
1891 }
1892
1893 impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
1894 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1895 let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1897 expr_path.path.segments.last()
1899 .map(|seg| seg.ident.to_string())
1900 .unwrap_or_default()
1901 } else {
1902 quote::quote!(#node.func).to_string()
1904 };
1905
1906 if let Some(filter) = self.name_filter {
1908 if func_name != filter {
1909 syn::visit::visit_expr_call(self, node);
1910 return;
1911 }
1912 }
1913
1914 let snippet = self.editor.format_expr_call(node);
1916 let location = self.editor.span_to_location(node.span());
1917
1918 self.results.push(InspectResult {
1919 file_path: String::new(), node_type: "ExprCall".to_string(),
1921 identifier: func_name,
1922 location,
1923 snippet,
1924 });
1925
1926 syn::visit::visit_expr_call(self, node);
1928 }
1929 }
1930
1931 let mut visitor = FunctionCallVisitor {
1932 results: &mut results,
1933 name_filter,
1934 editor: self,
1935 };
1936
1937 for item in &self.syntax_tree.items {
1939 syn::visit::visit_item(&mut visitor, item);
1940 }
1941 }
1942 "method-call" => {
1943 struct MethodCallVisitor<'a> {
1945 results: &'a mut Vec<InspectResult>,
1946 name_filter: Option<&'a str>,
1947 editor: &'a RustEditor,
1948 }
1949
1950 impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1951 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1952 let method_name = node.method.to_string();
1954
1955 if let Some(filter) = self.name_filter {
1957 if method_name != filter {
1958 syn::visit::visit_expr_method_call(self, node);
1959 return;
1960 }
1961 }
1962
1963 let snippet = self.editor.format_expr_method_call(node);
1965 let location = self.editor.span_to_location(node.span());
1966
1967 self.results.push(InspectResult {
1968 file_path: String::new(), node_type: "ExprMethodCall".to_string(),
1970 identifier: method_name,
1971 location,
1972 snippet,
1973 });
1974
1975 syn::visit::visit_expr_method_call(self, node);
1977 }
1978 }
1979
1980 let mut visitor = MethodCallVisitor {
1981 results: &mut results,
1982 name_filter,
1983 editor: self,
1984 };
1985
1986 for item in &self.syntax_tree.items {
1988 syn::visit::visit_item(&mut visitor, item);
1989 }
1990 }
1991 "identifier" => {
1992 struct IdentifierVisitor<'a> {
1994 results: &'a mut Vec<InspectResult>,
1995 name_filter: Option<&'a str>,
1996 editor: &'a RustEditor,
1997 }
1998
1999 impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2000 fn visit_ident(&mut self, node: &'ast syn::Ident) {
2001 let ident_name = node.to_string();
2003
2004 if let Some(filter) = self.name_filter {
2006 if ident_name != filter {
2007 syn::visit::visit_ident(self, node);
2008 return;
2009 }
2010 }
2011
2012 let snippet = self.editor.format_ident(node);
2014 let location = self.editor.span_to_location(node.span());
2015
2016 self.results.push(InspectResult {
2017 file_path: String::new(), node_type: "Ident".to_string(),
2019 identifier: ident_name,
2020 location,
2021 snippet,
2022 });
2023
2024 syn::visit::visit_ident(self, node);
2026 }
2027 }
2028
2029 let mut visitor = IdentifierVisitor {
2030 results: &mut results,
2031 name_filter,
2032 editor: self,
2033 };
2034
2035 for item in &self.syntax_tree.items {
2037 syn::visit::visit_item(&mut visitor, item);
2038 }
2039 }
2040 "type-ref" => {
2041 struct TypeRefVisitor<'a> {
2043 results: &'a mut Vec<InspectResult>,
2044 name_filter: Option<&'a str>,
2045 editor: &'a RustEditor,
2046 }
2047
2048 impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2049 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2050 let type_name = node.path.segments.last()
2052 .map(|seg| seg.ident.to_string())
2053 .unwrap_or_default();
2054
2055 if let Some(filter) = self.name_filter {
2057 if type_name != filter {
2058 syn::visit::visit_type_path(self, node);
2059 return;
2060 }
2061 }
2062
2063 let snippet = self.editor.format_type_path(node);
2065 let location = self.editor.span_to_location(node.span());
2066
2067 let path = &node.path;
2069 let path_str = quote::quote!(#path).to_string();
2070
2071 self.results.push(InspectResult {
2072 file_path: String::new(), node_type: "TypePath".to_string(),
2074 identifier: path_str.replace(" ", ""),
2075 location,
2076 snippet,
2077 });
2078
2079 syn::visit::visit_type_path(self, node);
2081 }
2082 }
2083
2084 let mut visitor = TypeRefVisitor {
2085 results: &mut results,
2086 name_filter,
2087 editor: self,
2088 };
2089
2090 for item in &self.syntax_tree.items {
2092 syn::visit::visit_item(&mut visitor, item);
2093 }
2094 }
2095 "macro-call" => {
2096 struct MacroCallVisitor<'a> {
2098 results: &'a mut Vec<InspectResult>,
2099 name_filter: Option<&'a str>,
2100 editor: &'a RustEditor,
2101 }
2102
2103 impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2104 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2105 let macro_name = node.mac.path.segments.last()
2107 .map(|seg| seg.ident.to_string())
2108 .unwrap_or_default();
2109
2110 if let Some(filter) = self.name_filter {
2112 if macro_name != filter {
2113 syn::visit::visit_expr_macro(self, node);
2114 return;
2115 }
2116 }
2117
2118 let snippet = self.editor.format_expr_macro(node);
2120 let location = self.editor.span_to_location(node.span());
2121
2122 self.results.push(InspectResult {
2123 file_path: String::new(), node_type: "ExprMacro".to_string(),
2125 identifier: macro_name,
2126 location,
2127 snippet,
2128 });
2129
2130 syn::visit::visit_expr_macro(self, node);
2132 }
2133
2134 fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2135 if let syn::Stmt::Macro(macro_stmt) = node {
2137 let macro_name = macro_stmt.mac.path.segments.last()
2138 .map(|seg| seg.ident.to_string())
2139 .unwrap_or_default();
2140
2141 if let Some(filter) = self.name_filter {
2143 if macro_name != filter {
2144 syn::visit::visit_stmt(self, node);
2145 return;
2146 }
2147 }
2148
2149 let snippet = self.editor.format_stmt_macro(macro_stmt);
2151 let location = self.editor.span_to_location(macro_stmt.span());
2152
2153 self.results.push(InspectResult {
2154 file_path: String::new(), node_type: "StmtMacro".to_string(),
2156 identifier: macro_name,
2157 location,
2158 snippet,
2159 });
2160 }
2161
2162 syn::visit::visit_stmt(self, node);
2164 }
2165 }
2166
2167 let mut visitor = MacroCallVisitor {
2168 results: &mut results,
2169 name_filter,
2170 editor: self,
2171 };
2172
2173 for item in &self.syntax_tree.items {
2175 syn::visit::visit_item(&mut visitor, item);
2176 }
2177 }
2178 _ => anyhow::bail!("Unsupported node type: {}", node_type),
2179 }
2180
2181 Ok(results)
2182 }
2183
2184 fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
2186 let start = self.span_to_byte_offset(expr.span().start());
2188 let end = self.span_to_byte_offset(expr.span().end());
2189
2190 let original = &self.content[start..end];
2192
2193 original.split_whitespace().collect::<Vec<_>>().join(" ")
2195 }
2196
2197 fn format_match_arm(&self, arm: &syn::Arm) -> String {
2199 let start = self.span_to_byte_offset(arm.span().start());
2201 let end = self.span_to_byte_offset(arm.span().end());
2202
2203 let original = &self.content[start..end];
2205
2206 original.split_whitespace().collect::<Vec<_>>().join(" ")
2208 }
2209
2210 fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2212 let start = self.span_to_byte_offset(expr.span().start());
2214 let end = self.span_to_byte_offset(expr.span().end());
2215
2216 let original = &self.content[start..end];
2218
2219 original.split_whitespace().collect::<Vec<_>>().join(" ")
2221 }
2222
2223 fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
2225 let start = self.span_to_byte_offset(expr.span().start());
2227 let end = self.span_to_byte_offset(expr.span().end());
2228
2229 let original = &self.content[start..end];
2231
2232 original.split_whitespace().collect::<Vec<_>>().join(" ")
2234 }
2235
2236 fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2238 let start = self.span_to_byte_offset(expr.span().start());
2240 let end = self.span_to_byte_offset(expr.span().end());
2241
2242 let original = &self.content[start..end];
2244
2245 original.split_whitespace().collect::<Vec<_>>().join(" ")
2247 }
2248
2249 fn format_ident(&self, ident: &syn::Ident) -> String {
2251 ident.to_string()
2252 }
2253
2254 fn format_type_path(&self, ty: &syn::TypePath) -> String {
2256 let start = self.span_to_byte_offset(ty.span().start());
2258 let end = self.span_to_byte_offset(ty.span().end());
2259
2260 let original = &self.content[start..end];
2262
2263 original.split_whitespace().collect::<Vec<_>>().join(" ")
2265 }
2266
2267 fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
2269 let start = self.span_to_byte_offset(expr.span().start());
2271 let end = self.span_to_byte_offset(expr.span().end());
2272
2273 let original = &self.content[start..end];
2275
2276 original.split_whitespace().collect::<Vec<_>>().join(" ")
2278 }
2279
2280 fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
2282 let start = self.span_to_byte_offset(stmt.span().start());
2284 let end = self.span_to_byte_offset(stmt.span().end());
2285
2286 let original = &self.content[start..end];
2288
2289 original.split_whitespace().collect::<Vec<_>>().join(" ")
2291 }
2292
2293 #[allow(dead_code)]
2295 pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2296 for (index, item) in self.syntax_tree.items.iter().enumerate() {
2297 match (node_type, item) {
2298 ("struct", Item::Struct(s)) if s.ident == name => {
2299 return Ok(index);
2300 }
2301 ("enum", Item::Enum(e)) if e.ident == name => {
2302 return Ok(index);
2303 }
2304 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2305 return Ok(index);
2306 }
2307 ("impl", Item::Impl(impl_block)) => {
2308 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2310 if let Some(segment) = type_path.path.segments.last() {
2311 if segment.ident == name {
2312 return Ok(index);
2313 }
2314 }
2315 }
2316 }
2317 _ => {}
2318 }
2319 }
2320
2321 anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2322 }
2323
2324 #[allow(dead_code)]
2326 pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2327 if index >= self.syntax_tree.items.len() {
2328 anyhow::bail!("Index {} out of bounds", index);
2329 }
2330
2331 self.syntax_tree.items[index] = new_item;
2333
2334 self.content = prettyplease::unparse(&self.syntax_tree);
2336
2337 self.line_offsets = Self::compute_line_offsets(&self.content);
2339
2340 Ok(())
2341 }
2342
2343 pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2344 let mut locations = Vec::new();
2345
2346 for item in &self.syntax_tree.items {
2347 match (node_type, item) {
2348 ("struct", Item::Struct(s)) if s.ident == name => {
2349 locations.push(self.span_to_location(s.span()));
2350 }
2351 ("enum", Item::Enum(e)) if e.ident == name => {
2352 locations.push(self.span_to_location(e.span()));
2353 }
2354 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2355 locations.push(self.span_to_location(f.span()));
2356 }
2357 _ => {}
2358 }
2359 }
2360
2361 if locations.is_empty() {
2362 anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2363 }
2364
2365 Ok(locations)
2366 }
2367
2368 fn span_to_location(&self, span: Span) -> NodeLocation {
2369 let start = span.start();
2370 let end = span.end();
2371
2372 NodeLocation {
2373 line: start.line,
2374 column: start.column,
2375 end_line: end.line,
2376 end_column: end.column,
2377 }
2378 }
2379
2380 pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
2382 use crate::operations::{InspectResult, TransformAction};
2383
2384 let matches = self.inspect(&op.node_type, op.name_filter.as_deref())?;
2386
2387 let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
2389 matches.into_iter()
2390 .filter(|m| m.snippet.contains(content_filter))
2391 .collect()
2392 } else {
2393 matches
2394 };
2395
2396 if filtered_matches.is_empty() {
2397 return Ok(ModificationResult {
2398 changed: false,
2399 modified_nodes: vec![],
2400 });
2401 }
2402
2403 let mut sorted_matches = filtered_matches;
2406 sorted_matches.sort_by(|a, b| {
2407 b.location.line.cmp(&a.location.line)
2408 .then(b.location.column.cmp(&a.location.column))
2409 });
2410
2411 let mut modified_nodes = Vec::new();
2412
2413 for match_result in &sorted_matches {
2414 let backup_node = BackupNode {
2416 node_type: match_result.node_type.clone(),
2417 identifier: match_result.identifier.clone(),
2418 original_content: match_result.snippet.clone(),
2419 location: match_result.location.clone(),
2420 };
2421
2422 let start_offset = self.line_column_to_byte_offset(
2424 match_result.location.line,
2425 match_result.location.column
2426 )?;
2427 let end_offset = self.line_column_to_byte_offset(
2428 match_result.location.end_line,
2429 match_result.location.end_column
2430 )?;
2431
2432 let original_text = &self.content[start_offset..end_offset];
2434
2435 let replacement = match &op.action {
2437 TransformAction::Comment => {
2438 format!("// {}", original_text.replace("\n", "\n// "))
2440 }
2441 TransformAction::Remove => {
2442 String::new()
2444 }
2445 TransformAction::Replace { with } => {
2446 with.clone()
2448 }
2449 };
2450
2451 self.content.replace_range(start_offset..end_offset, &replacement);
2453
2454 self.line_offsets = Self::compute_line_offsets(&self.content);
2456
2457 modified_nodes.push(backup_node);
2458 }
2459
2460 if !modified_nodes.is_empty() {
2462 }
2466
2467 Ok(ModificationResult {
2468 changed: !modified_nodes.is_empty(),
2469 modified_nodes,
2470 })
2471 }
2472
2473 fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
2475 if line == 0 || line > self.line_offsets.len() {
2476 anyhow::bail!("Line {} out of range", line);
2477 }
2478
2479 let line_start = self.line_offsets[line - 1];
2480 Ok(line_start + column)
2481 }
2482}
2483
2484struct MatchArmAdder {
2486 target_function: Option<String>,
2487 arm_to_add: Arm,
2488 modified: bool,
2489 current_function: Option<String>,
2490 modified_function: Option<String>,
2491}
2492
2493impl VisitMut for MatchArmAdder {
2494 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2495 let prev_fn = self.current_function.clone();
2496 self.current_function = Some(node.sig.ident.to_string());
2497
2498 syn::visit_mut::visit_item_fn_mut(self, node);
2500
2501 self.current_function = prev_fn;
2502 }
2503
2504 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2505 if let Some(ref target) = self.target_function {
2507 if self.current_function.as_ref() != Some(target) {
2508 syn::visit_mut::visit_expr_match_mut(self, node);
2510 return;
2511 }
2512 }
2513
2514 let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2516 let already_exists = node.arms.iter().any(|arm| {
2517 arm.pat.to_token_stream().to_string() == pattern_str
2518 });
2519
2520 if !already_exists {
2521 node.arms.push(self.arm_to_add.clone());
2523 self.modified = true;
2524 self.modified_function = self.current_function.clone();
2525 }
2526
2527 syn::visit_mut::visit_expr_match_mut(self, node);
2529 }
2530}
2531
2532struct MatchArmUpdater {
2534 target_function: Option<String>,
2535 pattern_to_match: String,
2536 new_body: syn::Expr,
2537 modified: bool,
2538 current_function: Option<String>,
2539 modified_function: Option<String>,
2540}
2541
2542impl VisitMut for MatchArmUpdater {
2543 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2544 let prev_fn = self.current_function.clone();
2545 self.current_function = Some(node.sig.ident.to_string());
2546
2547 syn::visit_mut::visit_item_fn_mut(self, node);
2548
2549 self.current_function = prev_fn;
2550 }
2551
2552 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2553 if let Some(ref target) = self.target_function {
2555 if self.current_function.as_ref() != Some(target) {
2556 syn::visit_mut::visit_expr_match_mut(self, node);
2557 return;
2558 }
2559 }
2560
2561 for arm in &mut node.arms {
2563 let pattern_str = arm.pat.to_token_stream().to_string();
2564 let pattern_normalized = pattern_str.replace(" ", "");
2566 let target_normalized = self.pattern_to_match.replace(" ", "");
2567
2568 if pattern_normalized == target_normalized {
2569 arm.body = Box::new(self.new_body.clone());
2570 self.modified = true;
2571 self.modified_function = self.current_function.clone();
2572 break;
2573 }
2574 }
2575
2576 syn::visit_mut::visit_expr_match_mut(self, node);
2577 }
2578}
2579
2580struct MatchArmRemover {
2582 target_function: Option<String>,
2583 pattern_to_remove: String,
2584 modified: bool,
2585 current_function: Option<String>,
2586 modified_function: Option<String>,
2587}
2588
2589impl VisitMut for MatchArmRemover {
2590 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2591 let prev_fn = self.current_function.clone();
2592 self.current_function = Some(node.sig.ident.to_string());
2593
2594 syn::visit_mut::visit_item_fn_mut(self, node);
2595
2596 self.current_function = prev_fn;
2597 }
2598
2599 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2600 if let Some(ref target) = self.target_function {
2602 if self.current_function.as_ref() != Some(target) {
2603 syn::visit_mut::visit_expr_match_mut(self, node);
2604 return;
2605 }
2606 }
2607
2608 let mut index_to_remove = None;
2610 for (i, arm) in node.arms.iter().enumerate() {
2611 let pattern_str = arm.pat.to_token_stream().to_string();
2612 let pattern_normalized = pattern_str.replace(" ", "");
2614 let target_normalized = self.pattern_to_remove.replace(" ", "");
2615
2616 if pattern_normalized == target_normalized {
2617 index_to_remove = Some(i);
2618 break;
2619 }
2620 }
2621
2622 if let Some(index) = index_to_remove {
2623 node.arms.remove(index);
2624 self.modified = true;
2625 self.modified_function = self.current_function.clone();
2626 }
2627
2628 syn::visit_mut::visit_expr_match_mut(self, node);
2629 }
2630}
2631
2632struct MultiMatchArmAdder {
2634 target_function: Option<String>,
2635 arms_to_add: Vec<(String, Arm)>, modified: bool,
2637 current_function: Option<String>,
2638 modified_function: Option<String>,
2639}
2640
2641impl VisitMut for MultiMatchArmAdder {
2642 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2643 let prev_fn = self.current_function.clone();
2644 self.current_function = Some(node.sig.ident.to_string());
2645
2646 syn::visit_mut::visit_item_fn_mut(self, node);
2647
2648 self.current_function = prev_fn;
2649 }
2650
2651 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2652 if let Some(ref target) = self.target_function {
2654 if self.current_function.as_ref() != Some(target) {
2655 syn::visit_mut::visit_expr_match_mut(self, node);
2656 return;
2657 }
2658 }
2659
2660 for (pattern_str, arm) in &self.arms_to_add {
2662 let already_exists = node.arms.iter().any(|existing_arm| {
2664 existing_arm.pat.to_token_stream().to_string() == *pattern_str
2665 });
2666
2667 if !already_exists {
2668 node.arms.push(arm.clone());
2669 self.modified = true;
2670 self.modified_function = self.current_function.clone();
2671 }
2672 }
2673
2674 syn::visit_mut::visit_expr_match_mut(self, node);
2675 }
2676}
2677
2678struct StructLiteralFieldAdder {
2680 struct_name: String,
2681 field_def: String,
2682 field_name: String,
2683 position: InsertPosition,
2684 modified: bool,
2685}
2686
2687impl VisitMut for StructLiteralFieldAdder {
2688 fn visit_expr_mut(&mut self, node: &mut Expr) {
2689 if let Expr::Struct(expr_struct) = node {
2691 let is_match = if self.struct_name.contains("::") {
2697 if self.struct_name.starts_with("*::") {
2699 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
2702 .map(|seg| seg.ident.to_string() == target_name)
2703 .unwrap_or(false)
2704 } else {
2705 let path_str = expr_struct.path.segments.iter()
2707 .map(|seg| seg.ident.to_string())
2708 .collect::<Vec<_>>()
2709 .join("::");
2710 path_str == self.struct_name
2711 }
2712 } else {
2713 expr_struct.path.segments.len() == 1
2715 && expr_struct.path.segments.last()
2716 .map(|seg| seg.ident.to_string())
2717 .as_ref() == Some(&self.struct_name)
2718 };
2719
2720 if is_match {
2721 let field_exists = expr_struct.fields.iter().any(|fv| {
2723 fv.member.to_token_stream().to_string() == self.field_name
2724 });
2725
2726 if !field_exists {
2727 let field_value_code = format!("{{ {} }}", self.field_def);
2730 if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
2731 if let Some(new_fv) = expr.fields.first() {
2732 match &self.position {
2734 InsertPosition::First => {
2735 expr_struct.fields.insert(0, new_fv.clone());
2736 self.modified = true;
2737 }
2738 InsertPosition::Last => {
2739 expr_struct.fields.push(new_fv.clone());
2740 self.modified = true;
2741 }
2742 InsertPosition::After(after_field) => {
2743 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2745 fv.member.to_token_stream().to_string() == *after_field
2746 }) {
2747 expr_struct.fields.insert(pos + 1, new_fv.clone());
2748 self.modified = true;
2749 }
2750 }
2751 InsertPosition::Before(before_field) => {
2752 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2754 fv.member.to_token_stream().to_string() == *before_field
2755 }) {
2756 expr_struct.fields.insert(pos, new_fv.clone());
2757 self.modified = true;
2758 }
2759 }
2760 }
2761 }
2762 }
2763 }
2764 }
2765 }
2766
2767 syn::visit_mut::visit_expr_mut(self, node);
2770 }
2771}