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 Operation::RenameEnumVariant(op) => self.rename_enum_variant(op),
85 }
86 }
87
88 pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
89 let mut modified_nodes = Vec::new();
90
91 let item_struct = self.syntax_tree.items.iter()
93 .find_map(|item| {
94 if let Item::Struct(s) = item {
95 if s.ident == op.struct_name {
96 return Some(s.clone());
97 }
98 }
99 None
100 })
101 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
102
103 if let Some(ref where_filter) = op.where_filter {
105 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
106 return Ok(ModificationResult {
108 changed: false,
109 modified_nodes: vec![],
110 });
111 }
112 }
113
114 if op.literal_default.is_none() {
116 let backup_node = BackupNode {
118 node_type: "ItemStruct".to_string(),
119 identifier: op.struct_name.clone(),
120 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
121 location: self.span_to_location(item_struct.span()),
122 };
123
124 let modified = self.insert_struct_field(&item_struct, op)
126 .context("Failed to add field to struct definition")?;
127
128 if !modified {
129 return Ok(ModificationResult {
130 changed: false,
131 modified_nodes: vec![],
132 });
133 }
134
135 return Ok(ModificationResult {
136 changed: true,
137 modified_nodes: vec![backup_node],
138 });
139 }
140
141 let literal_default = op.literal_default.as_ref().unwrap();
145
146 let has_type = op.field_def.contains(':');
149
150 let mut def_modified = false;
151 if has_type {
152 let backup_node = BackupNode {
154 node_type: "ItemStruct".to_string(),
155 identifier: op.struct_name.clone(),
156 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
157 location: self.span_to_location(item_struct.span()),
158 };
159
160 def_modified = self.insert_struct_field(&item_struct, op)
162 .context("Failed to add field to struct definition")?;
163
164 if def_modified {
165 modified_nodes.push(backup_node);
166 self.syntax_tree = syn::parse_str(&self.content)
168 .context("Failed to re-parse content after adding struct field")?;
169 self.line_offsets = Self::compute_line_offsets(&self.content);
170 }
171 }
172
173 let field_name = op.field_def.split(':')
176 .next()
177 .map(|s| s.trim().to_string())
178 .context("Failed to extract field name from field definition")?;
179
180 let literal_op = AddStructLiteralFieldOp {
182 struct_name: op.struct_name.clone(),
183 field_def: format!("{}: {}", field_name, literal_default),
184 position: op.position.clone(),
185 };
186
187 let literal_result = self.add_struct_literal_field(&literal_op)
189 .context("Failed to update struct literals")?;
190 modified_nodes.extend(literal_result.modified_nodes);
191
192 Ok(ModificationResult {
193 changed: true,
194 modified_nodes,
195 })
196 }
197
198 fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
199 if let Fields::Named(ref fields) = item_struct.fields {
200 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
202 let dummy: ItemStruct = parse_str(&field_code)
203 .context("Failed to parse field definition")?;
204
205 let new_field = if let Fields::Named(ref nf) = dummy.fields {
206 nf.named.first()
207 .context("No field found in definition")?
208 .clone()
209 } else {
210 anyhow::bail!("Expected named field");
211 };
212
213 let new_field_name = new_field.ident.as_ref()
215 .map(|i| i.to_string())
216 .context("Field must have a name")?;
217
218 if fields.named.iter().any(|f| {
219 f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
220 }) {
221 return Ok(false);
223 }
224
225 let insert_pos = match &op.position {
227 InsertPosition::First => {
228 if let Some(first_field) = fields.named.first() {
229 self.span_to_byte_offset(first_field.span().start())
230 } else {
231 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
233 brace_pos + 1
234 }
235 }
236 InsertPosition::Last => {
237 if let Some(last_field) = fields.named.last() {
238 let end = self.span_to_byte_offset(last_field.span().end());
239 self.find_after_field_end(end)
241 } else {
242 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
244 brace_pos + 1
245 }
246 }
247 InsertPosition::After(name) => {
248 let field = fields.named.iter()
249 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
250 .with_context(|| format!("Field '{}' not found", name))?;
251 let end = self.span_to_byte_offset(field.span().end());
252 self.find_after_field_end(end)
253 }
254 InsertPosition::Before(name) => {
255 let field = fields.named.iter()
256 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
257 .with_context(|| format!("Field '{}' not found", name))?;
258 self.span_to_byte_offset(field.span().start())
259 }
260 };
261
262 let indent = self.get_indentation(insert_pos);
264 let field_str = Self::format_field(&new_field);
265 let insert_text = if matches!(op.position, InsertPosition::First) {
266 format!("\n{}{},", indent, field_str)
267 } else {
268 format!("\n{}{},", indent, field_str)
269 };
270
271 self.content.insert_str(insert_pos, &insert_text);
272 return Ok(true);
273 }
274
275 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
276 }
277
278 pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
279 let item_struct = self.syntax_tree.items.iter()
281 .find_map(|item| {
282 if let Item::Struct(s) = item {
283 if s.ident == op.struct_name {
284 return Some(s.clone());
285 }
286 }
287 None
288 })
289 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
290
291 if let Some(ref where_filter) = op.where_filter {
293 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
294 return Ok(ModificationResult {
296 changed: false,
297 modified_nodes: vec![],
298 });
299 }
300 }
301
302 let backup_node = BackupNode {
304 node_type: "ItemStruct".to_string(),
305 identifier: op.struct_name.clone(),
306 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
307 location: self.span_to_location(item_struct.span()),
308 };
309
310 let modified = self.replace_struct_field(&item_struct, op)?;
311
312 Ok(ModificationResult {
313 changed: modified,
314 modified_nodes: if modified { vec![backup_node] } else { vec![] },
315 })
316 }
317
318 fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
319 if let Fields::Named(ref fields) = item_struct.fields {
320 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
322 let dummy: ItemStruct = parse_str(&field_code)
323 .context("Failed to parse field definition")?;
324
325 let new_field = if let Fields::Named(ref nf) = dummy.fields {
326 nf.named.first()
327 .context("No field found in definition")?
328 .clone()
329 } else {
330 anyhow::bail!("Expected named field");
331 };
332
333 let field_name = new_field.ident.as_ref()
335 .map(|i| i.to_string())
336 .context("Field must have a name")?;
337
338 let existing_field = fields.named.iter()
340 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
341 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
342
343 let start = self.span_to_byte_offset(existing_field.span().start());
345 let end = self.span_to_byte_offset(existing_field.span().end());
346
347 let new_field_str = Self::format_field(&new_field);
349
350 self.content.replace_range(start..end, &new_field_str);
352
353 return Ok(true);
354 }
355
356 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
357 }
358
359 pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
360 let item_struct = self.syntax_tree.items.iter()
362 .find_map(|item| {
363 if let Item::Struct(s) = item {
364 if s.ident == op.struct_name {
365 return Some(s.clone());
366 }
367 }
368 None
369 })
370 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
371
372 if let Some(ref where_filter) = op.where_filter {
374 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
375 return Ok(ModificationResult {
377 changed: false,
378 modified_nodes: vec![],
379 });
380 }
381 }
382
383 let backup_node = BackupNode {
385 node_type: "ItemStruct".to_string(),
386 identifier: op.struct_name.clone(),
387 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
388 location: self.span_to_location(item_struct.span()),
389 };
390
391 if let Fields::Named(ref fields) = item_struct.fields {
392 let field_to_remove = fields.named.iter()
394 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
395 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
396
397 let start = self.span_to_byte_offset(field_to_remove.span().start());
399 let mut end = self.span_to_byte_offset(field_to_remove.span().end());
400
401 while end < self.content.len() {
403 match self.content.as_bytes()[end] as char {
404 ',' => {
405 end += 1;
406 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
408 end += 1;
409 }
410 break;
411 }
412 ' ' | '\t' => end += 1,
413 '\n' => {
414 end += 1;
415 break;
416 }
417 _ => break,
418 }
419 }
420
421 let mut line_start = start;
423 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
424 line_start -= 1;
425 }
426
427 let before_field = &self.content[line_start..start];
429 if before_field.trim().is_empty() {
430 self.content.replace_range(line_start..end, "");
432 } else {
433 self.content.replace_range(start..end, "");
435 }
436
437 return Ok(ModificationResult {
438 changed: true,
439 modified_nodes: vec![backup_node],
440 });
441 }
442
443 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
444 }
445
446 pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
447 let field_name = op.field_def.split(':')
449 .next()
450 .map(|s| s.trim().to_string())
451 .context("Field definition must contain ':'")?;
452
453 let backup_nodes = self.collect_struct_literal_backups(&op.struct_name);
455
456 let mut visitor = StructLiteralFieldAdder {
458 struct_name: op.struct_name.clone(),
459 field_def: op.field_def.clone(),
460 field_name,
461 position: op.position.clone(),
462 modified: false,
463 };
464
465 visitor.visit_file_mut(&mut self.syntax_tree);
466
467 if visitor.modified {
468 self.content = prettyplease::unparse(&self.syntax_tree);
470 Ok(ModificationResult {
471 changed: true,
472 modified_nodes: backup_nodes,
473 })
474 } else {
475 Ok(ModificationResult {
476 changed: false,
477 modified_nodes: vec![],
478 })
479 }
480 }
481
482 fn collect_struct_literal_backups(&self, struct_name: &str) -> Vec<BackupNode> {
484 use syn::visit::Visit;
485
486 struct LiteralCollector {
487 struct_name: String,
488 backups: Vec<BackupNode>,
489 counter: usize,
490 }
491
492 impl<'ast> Visit<'ast> for LiteralCollector {
493 fn visit_expr(&mut self, node: &'ast Expr) {
494 if let Expr::Struct(expr_struct) = node {
495 let matches = if self.struct_name.contains("::") {
501 if self.struct_name.starts_with("*::") {
503 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
506 .map(|seg| seg.ident.to_string() == target_name)
507 .unwrap_or(false)
508 } else {
509 let path_str = expr_struct.path.segments.iter()
511 .map(|seg| seg.ident.to_string())
512 .collect::<Vec<_>>()
513 .join("::");
514 path_str == self.struct_name
515 }
516 } else {
517 expr_struct.path.segments.len() == 1
519 && expr_struct.path.segments.last()
520 .map(|seg| seg.ident.to_string() == self.struct_name)
521 .unwrap_or(false)
522 };
523
524 if matches {
525 self.backups.push(BackupNode {
526 node_type: "ExprStruct".to_string(),
527 identifier: format!("{}#{}", self.struct_name, self.counter),
528 original_content: expr_struct.to_token_stream().to_string(),
529 location: NodeLocation {
530 line: 0, column: 0,
532 end_line: 0,
533 end_column: 0,
534 },
535 });
536 self.counter += 1;
537 }
538 }
539 syn::visit::visit_expr(self, node);
540 }
541 }
542
543 let mut collector = LiteralCollector {
544 struct_name: struct_name.to_string(),
545 backups: Vec::new(),
546 counter: 0,
547 };
548
549 collector.visit_file(&self.syntax_tree);
550 collector.backups
551 }
552
553 pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
554 let item_enum = self.syntax_tree.items.iter()
556 .find_map(|item| {
557 if let Item::Enum(e) = item {
558 if e.ident == op.enum_name {
559 return Some(e.clone());
560 }
561 }
562 None
563 })
564 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
565
566 if let Some(ref where_filter) = op.where_filter {
568 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
569 return Ok(ModificationResult {
571 changed: false,
572 modified_nodes: vec![],
573 });
574 }
575 }
576
577 let backup_node = BackupNode {
579 node_type: "ItemEnum".to_string(),
580 identifier: op.enum_name.clone(),
581 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
582 location: self.span_to_location(item_enum.span()),
583 };
584
585 let modified = self.insert_enum_variant(&item_enum, op)?;
586
587 Ok(ModificationResult {
588 changed: modified,
589 modified_nodes: if modified { vec![backup_node] } else { vec![] },
590 })
591 }
592
593 fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
594 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
596 let dummy: ItemEnum = parse_str(&variant_code)
597 .context("Failed to parse variant definition")?;
598
599 let new_variant = dummy.variants.first()
600 .context("No variant found in definition")?
601 .clone();
602
603 let variant_name = new_variant.ident.to_string();
605 if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
606 return Ok(false);
608 }
609
610 let insert_pos = match &op.position {
612 InsertPosition::First => {
613 if let Some(first_var) = item_enum.variants.first() {
614 self.span_to_byte_offset(first_var.span().start())
615 } else {
616 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
617 brace_pos + 1
618 }
619 }
620 InsertPosition::Last => {
621 if let Some(last_var) = item_enum.variants.last() {
622 let end = self.span_to_byte_offset(last_var.span().end());
623 self.find_after_field_end(end)
624 } else {
625 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
626 brace_pos + 1
627 }
628 }
629 InsertPosition::After(name) => {
630 let variant = item_enum.variants.iter()
631 .find(|v| v.ident.to_string() == *name)
632 .with_context(|| format!("Variant '{}' not found", name))?;
633 let end = self.span_to_byte_offset(variant.span().end());
634 self.find_after_field_end(end)
635 }
636 InsertPosition::Before(name) => {
637 let variant = item_enum.variants.iter()
638 .find(|v| v.ident.to_string() == *name)
639 .with_context(|| format!("Variant '{}' not found", name))?;
640 self.span_to_byte_offset(variant.span().start())
641 }
642 };
643
644 let indent = self.get_indentation(insert_pos);
645 let variant_str = new_variant.to_token_stream().to_string();
646 let insert_text = format!("\n{}{},", indent, variant_str);
647
648 self.content.insert_str(insert_pos, &insert_text);
649 Ok(true)
650 }
651
652 fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
653 let item_enum = self.syntax_tree.items.iter()
655 .find_map(|item| {
656 if let Item::Enum(e) = item {
657 if e.ident == op.enum_name {
658 return Some(e.clone());
659 }
660 }
661 None
662 })
663 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
664
665 if let Some(ref where_filter) = op.where_filter {
667 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
668 return Ok(ModificationResult {
670 changed: false,
671 modified_nodes: vec![],
672 });
673 }
674 }
675
676 let backup_node = BackupNode {
678 node_type: "ItemEnum".to_string(),
679 identifier: op.enum_name.clone(),
680 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
681 location: self.span_to_location(item_enum.span()),
682 };
683
684 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
686 let dummy: ItemEnum = parse_str(&variant_code)
687 .context("Failed to parse variant definition")?;
688
689 let new_variant = dummy.variants.first()
690 .context("No variant found in definition")?
691 .clone();
692
693 let variant_name = new_variant.ident.to_string();
694
695 let existing_variant = item_enum.variants.iter()
697 .find(|v| v.ident.to_string() == variant_name)
698 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
699
700 let start = self.span_to_byte_offset(existing_variant.span().start());
702 let end = self.span_to_byte_offset(existing_variant.span().end());
703
704 let variant_str = new_variant.to_token_stream().to_string();
706 self.content.replace_range(start..end, &variant_str);
707
708 Ok(ModificationResult {
709 changed: true,
710 modified_nodes: vec![backup_node],
711 })
712 }
713
714 pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
715 let item_enum = self.syntax_tree.items.iter()
717 .find_map(|item| {
718 if let Item::Enum(e) = item {
719 if e.ident == op.enum_name {
720 return Some(e.clone());
721 }
722 }
723 None
724 })
725 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
726
727 if let Some(ref where_filter) = op.where_filter {
729 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
730 return Ok(ModificationResult {
732 changed: false,
733 modified_nodes: vec![],
734 });
735 }
736 }
737
738 let backup_node = BackupNode {
740 node_type: "ItemEnum".to_string(),
741 identifier: op.enum_name.clone(),
742 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
743 location: self.span_to_location(item_enum.span()),
744 };
745
746 let variant_to_remove = item_enum.variants.iter()
748 .find(|v| v.ident.to_string() == op.variant_name)
749 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
750
751 let start = self.span_to_byte_offset(variant_to_remove.span().start());
753 let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
754
755 while end < self.content.len() {
757 match self.content.as_bytes()[end] as char {
758 ',' => {
759 end += 1;
760 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
761 end += 1;
762 }
763 break;
764 }
765 ' ' | '\t' => end += 1,
766 '\n' => {
767 end += 1;
768 break;
769 }
770 _ => break,
771 }
772 }
773
774 let mut line_start = start;
776 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
777 line_start -= 1;
778 }
779
780 let before_variant = &self.content[line_start..start];
781 if before_variant.trim().is_empty() {
782 self.content.replace_range(line_start..end, "");
783 } else {
784 self.content.replace_range(start..end, "");
785 }
786
787 Ok(ModificationResult {
788 changed: true,
789 modified_nodes: vec![backup_node],
790 })
791 }
792
793 pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
794 if op.auto_detect {
795 self.add_missing_match_arms(op)
797 } else {
798 self.add_single_match_arm(op)
800 }
801 }
802
803 fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
804 let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
806 let expr: syn::Expr = parse_str(&dummy_match)
807 .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
808
809 let arm = if let syn::Expr::Match(match_expr) = expr {
811 match_expr.arms.into_iter().next()
812 .context("Failed to extract arm from dummy match")?
813 } else {
814 anyhow::bail!("Expected match expression");
815 };
816
817 let backup_node = if let Some(ref fn_name) = op.function_name {
819 self.get_function_backup(fn_name)?
820 } else {
821 BackupNode {
824 node_type: "Unknown".to_string(),
825 identifier: "match_expression".to_string(),
826 original_content: String::new(),
827 location: NodeLocation {
828 line: 0,
829 column: 0,
830 end_line: 0,
831 end_column: 0,
832 },
833 }
834 };
835
836 let mut visitor = MatchArmAdder {
838 target_function: op.function_name.clone(),
839 arm_to_add: arm,
840 modified: false,
841 current_function: None,
842 modified_function: None,
843 };
844
845 visitor.visit_file_mut(&mut self.syntax_tree);
846
847 if visitor.modified {
848 self.replace_modified_functions(&visitor.modified_function)?;
850 Ok(ModificationResult {
851 changed: true,
852 modified_nodes: vec![backup_node],
853 })
854 } else {
855 Ok(ModificationResult {
856 changed: false,
857 modified_nodes: vec![],
858 })
859 }
860 }
861
862 fn unparse_item(&self, item: &Item) -> String {
864 let temp_file = syn::File {
865 shebang: None,
866 attrs: Vec::new(),
867 items: vec![item.clone()],
868 };
869 prettyplease::unparse(&temp_file).trim().to_string()
870 }
871
872 fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
874 for item in &self.syntax_tree.items {
875 if let Item::Fn(f) = item {
876 if f.sig.ident == fn_name {
877 return Ok(BackupNode {
878 node_type: "ItemFn".to_string(),
879 identifier: fn_name.to_string(),
880 original_content: self.unparse_item(&Item::Fn(f.clone())),
881 location: self.span_to_location(f.span()),
882 });
883 }
884 }
885 }
886 anyhow::bail!("Function '{}' not found", fn_name)
887 }
888
889 fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
890 let enum_name = op.enum_name.as_ref()
892 .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
893
894 let enum_variants = self.find_enum_variants(enum_name)?;
896
897 if enum_variants.is_empty() {
898 anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
899 }
900
901 let existing_patterns = self.find_existing_match_patterns(&op.function_name);
903
904 let mut missing_variants = Vec::new();
906 for variant in &enum_variants {
907 let pattern = format!("{}::{}", enum_name, variant);
908 let pattern_normalized = pattern.replace(" ", "");
909
910 let exists = existing_patterns.iter().any(|p| {
911 p.replace(" ", "") == pattern_normalized
912 });
913
914 if !exists {
915 missing_variants.push(variant.clone());
916 }
917 }
918
919 if missing_variants.is_empty() {
920 println!("All enum variants already covered in match expressions");
921 return Ok(ModificationResult {
922 changed: false,
923 modified_nodes: vec![],
924 });
925 }
926
927 let backup_node = if let Some(ref fn_name) = op.function_name {
929 self.get_function_backup(fn_name)?
930 } else {
931 BackupNode {
932 node_type: "Unknown".to_string(),
933 identifier: "match_expression".to_string(),
934 original_content: String::new(),
935 location: NodeLocation {
936 line: 0,
937 column: 0,
938 end_line: 0,
939 end_column: 0,
940 },
941 }
942 };
943
944 let mut arms_to_add = Vec::new();
946 for variant in &missing_variants {
947 let pattern = format!("{}::{}", enum_name, variant);
948 let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
949 let expr: syn::Expr = parse_str(&dummy_match)
950 .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
951
952 if let syn::Expr::Match(match_expr) = expr {
953 if let Some(arm) = match_expr.arms.into_iter().next() {
954 arms_to_add.push((pattern.clone(), arm));
955 }
956 }
957 }
958
959 let mut visitor = MultiMatchArmAdder {
961 target_function: op.function_name.clone(),
962 arms_to_add,
963 modified: false,
964 current_function: None,
965 modified_function: None,
966 };
967
968 visitor.visit_file_mut(&mut self.syntax_tree);
969
970 if visitor.modified {
971 for variant in &missing_variants {
973 println!("Added match arm for: {}::{}", enum_name, variant);
974 }
975
976 self.replace_modified_functions(&visitor.modified_function)?;
978 Ok(ModificationResult {
979 changed: true,
980 modified_nodes: vec![backup_node],
981 })
982 } else {
983 Ok(ModificationResult {
984 changed: false,
985 modified_nodes: vec![],
986 })
987 }
988 }
989
990 fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
991 for item in &self.syntax_tree.items {
993 if let Item::Enum(e) = item {
994 if e.ident == enum_name {
995 let variants: Vec<String> = e.variants.iter()
996 .map(|v| v.ident.to_string())
997 .collect();
998 return Ok(variants);
999 }
1000 }
1001 }
1002
1003 Ok(Vec::new())
1004 }
1005
1006 fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1007 use syn::visit::Visit;
1008
1009 struct PatternCollector {
1010 target_function: Option<String>,
1011 current_function: Option<String>,
1012 patterns: Vec<String>,
1013 }
1014
1015 impl<'ast> Visit<'ast> for PatternCollector {
1016 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1017 let prev_fn = self.current_function.clone();
1018 self.current_function = Some(node.sig.ident.to_string());
1019 syn::visit::visit_item_fn(self, node);
1020 self.current_function = prev_fn;
1021 }
1022
1023 fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1024 if let Some(ref target) = self.target_function {
1026 if self.current_function.as_ref() != Some(target) {
1027 syn::visit::visit_expr_match(self, node);
1028 return;
1029 }
1030 }
1031
1032 for arm in &node.arms {
1034 self.patterns.push(arm.pat.to_token_stream().to_string());
1035 }
1036
1037 syn::visit::visit_expr_match(self, node);
1038 }
1039 }
1040
1041 let mut collector = PatternCollector {
1042 target_function: function_name.clone(),
1043 current_function: None,
1044 patterns: Vec::new(),
1045 };
1046
1047 collector.visit_file(&self.syntax_tree);
1048 collector.patterns
1049 }
1050
1051 pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1052 let backup_node = if let Some(ref fn_name) = op.function_name {
1054 self.get_function_backup(fn_name)?
1055 } else {
1056 BackupNode {
1057 node_type: "Unknown".to_string(),
1058 identifier: "match_expression".to_string(),
1059 original_content: String::new(),
1060 location: NodeLocation {
1061 line: 0,
1062 column: 0,
1063 end_line: 0,
1064 end_column: 0,
1065 },
1066 }
1067 };
1068
1069 let new_body: syn::Expr = parse_str(&op.new_body)
1071 .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1072
1073 let mut visitor = MatchArmUpdater {
1075 target_function: op.function_name.clone(),
1076 pattern_to_match: op.pattern.clone(),
1077 new_body,
1078 modified: false,
1079 current_function: None,
1080 modified_function: None,
1081 };
1082
1083 visitor.visit_file_mut(&mut self.syntax_tree);
1084
1085 if visitor.modified {
1086 self.replace_modified_functions(&visitor.modified_function)?;
1088 Ok(ModificationResult {
1089 changed: true,
1090 modified_nodes: vec![backup_node],
1091 })
1092 } else {
1093 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1094 }
1095 }
1096
1097 pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1098 let backup_node = if let Some(ref fn_name) = op.function_name {
1100 self.get_function_backup(fn_name)?
1101 } else {
1102 BackupNode {
1103 node_type: "Unknown".to_string(),
1104 identifier: "match_expression".to_string(),
1105 original_content: String::new(),
1106 location: NodeLocation {
1107 line: 0,
1108 column: 0,
1109 end_line: 0,
1110 end_column: 0,
1111 },
1112 }
1113 };
1114
1115 let mut visitor = MatchArmRemover {
1117 target_function: op.function_name.clone(),
1118 pattern_to_remove: op.pattern.clone(),
1119 modified: false,
1120 current_function: None,
1121 modified_function: None,
1122 };
1123
1124 visitor.visit_file_mut(&mut self.syntax_tree);
1125
1126 if visitor.modified {
1127 self.replace_modified_functions(&visitor.modified_function)?;
1129 Ok(ModificationResult {
1130 changed: true,
1131 modified_nodes: vec![backup_node],
1132 })
1133 } else {
1134 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1135 }
1136 }
1137
1138 pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1139 let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1141 let dummy: syn::ItemImpl = parse_str(&method_code)
1142 .context("Failed to parse method definition")?;
1143
1144 let new_method = dummy.items.first()
1145 .context("No method found in definition")?
1146 .clone();
1147
1148 let method_name = match &new_method {
1150 syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1151 _ => anyhow::bail!("Only method definitions are supported"),
1152 };
1153
1154 let impl_index = self.syntax_tree.items.iter().position(|item| {
1156 if let Item::Impl(impl_block) = item {
1157 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1159 if let Some(segment) = type_path.path.segments.last() {
1160 return segment.ident == op.target;
1161 }
1162 }
1163 }
1164 false
1165 }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1166
1167 let impl_block = match &self.syntax_tree.items[impl_index] {
1169 Item::Impl(i) => i,
1170 _ => unreachable!(),
1171 };
1172
1173 let method_exists = impl_block.items.iter().any(|item| {
1174 if let syn::ImplItem::Fn(f) = item {
1175 f.sig.ident == method_name
1176 } else {
1177 false
1178 }
1179 });
1180
1181 if method_exists {
1182 return Ok(ModificationResult {
1183 changed: false,
1184 modified_nodes: vec![],
1185 });
1186 }
1187
1188 let backup_node = BackupNode {
1190 node_type: "ItemImpl".to_string(),
1191 identifier: op.target.clone(),
1192 original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1193 location: self.span_to_location(impl_block.span()),
1194 };
1195
1196 let impl_span = impl_block.span();
1198
1199 match &mut self.syntax_tree.items[impl_index] {
1201 Item::Impl(impl_block) => {
1202 match &op.position {
1204 InsertPosition::First => {
1205 impl_block.items.insert(0, new_method);
1206 }
1207 InsertPosition::Last => {
1208 impl_block.items.push(new_method);
1209 }
1210 InsertPosition::After(name) => {
1211 let pos = impl_block.items.iter().position(|item| {
1212 if let syn::ImplItem::Fn(f) = item {
1213 f.sig.ident == name
1214 } else {
1215 false
1216 }
1217 }).with_context(|| format!("Method '{}' not found", name))?;
1218 impl_block.items.insert(pos + 1, new_method);
1219 }
1220 InsertPosition::Before(name) => {
1221 let pos = impl_block.items.iter().position(|item| {
1222 if let syn::ImplItem::Fn(f) = item {
1223 f.sig.ident == name
1224 } else {
1225 false
1226 }
1227 }).with_context(|| format!("Method '{}' not found", name))?;
1228 impl_block.items.insert(pos, new_method);
1229 }
1230 }
1231 }
1232 _ => unreachable!(),
1233 }
1234
1235 self.replace_formatted_item(impl_index, impl_span)?;
1237
1238 Ok(ModificationResult {
1239 changed: true,
1240 modified_nodes: vec![backup_node],
1241 })
1242 }
1243
1244 pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1245 let use_code = format!("use {};", op.use_path);
1247 let use_item: syn::ItemUse = parse_str(&use_code)
1248 .context("Failed to parse use statement")?;
1249
1250 let use_exists = self.syntax_tree.items.iter().any(|item| {
1252 if let Item::Use(existing_use) = item {
1253 existing_use.tree.to_token_stream().to_string() ==
1255 use_item.tree.to_token_stream().to_string()
1256 } else {
1257 false
1258 }
1259 });
1260
1261 if use_exists {
1262 return Ok(ModificationResult {
1263 changed: false,
1264 modified_nodes: vec![],
1265 });
1266 }
1267
1268 let backup_node = BackupNode {
1270 node_type: "ItemUse".to_string(),
1271 identifier: op.use_path.clone(),
1272 original_content: format!("use {};", op.use_path),
1273 location: NodeLocation {
1274 line: 0,
1275 column: 0,
1276 end_line: 0,
1277 end_column: 0,
1278 },
1279 };
1280
1281 let insert_index = match &op.position {
1283 InsertPosition::First => 0,
1284 InsertPosition::Last => {
1285 self.syntax_tree.items.iter()
1287 .rposition(|item| matches!(item, Item::Use(_)))
1288 .map(|i| i + 1)
1289 .unwrap_or(0)
1290 }
1291 InsertPosition::After(path) => {
1292 let pos = self.syntax_tree.items.iter().position(|item| {
1294 if let Item::Use(u) = item {
1295 u.tree.to_token_stream().to_string().contains(path)
1296 } else {
1297 false
1298 }
1299 }).with_context(|| format!("Use statement for '{}' not found", path))?;
1300 pos + 1
1301 }
1302 InsertPosition::Before(path) => {
1303 self.syntax_tree.items.iter().position(|item| {
1305 if let Item::Use(u) = item {
1306 u.tree.to_token_stream().to_string().contains(path)
1307 } else {
1308 false
1309 }
1310 }).with_context(|| format!("Use statement for '{}' not found", path))?
1311 }
1312 };
1313
1314 self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1316
1317 let insert_line_pos = if insert_index == 0 {
1320 0
1322 } else {
1323 let prev_item = &self.syntax_tree.items[insert_index - 1];
1325 let span = prev_item.span();
1326 let end_pos = self.span_to_byte_offset(span.end());
1327
1328 let mut line_end = end_pos;
1330 while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1331 line_end += 1;
1332 }
1333 if line_end < self.content.len() {
1335 line_end + 1
1336 } else {
1337 self.content.push('\n');
1339 self.content.len()
1340 }
1341 };
1342
1343 let use_str = format!("use {};\n", op.use_path);
1345
1346 self.content.insert_str(insert_line_pos, &use_str);
1348
1349 Ok(ModificationResult {
1350 changed: true,
1351 modified_nodes: vec![backup_node],
1352 })
1353 }
1354
1355 pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1356 let item_index = self.syntax_tree.items.iter().position(|item| {
1358 match (&op.target_type as &str, item) {
1359 ("struct", Item::Struct(s)) => s.ident == op.target_name,
1360 ("enum", Item::Enum(e)) => e.ident == op.target_name,
1361 _ => false,
1362 }
1363 }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1364
1365 let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1367 Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1368 Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1369 _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1370 };
1371
1372 if let Some(ref where_filter) = op.where_filter {
1374 if !self.matches_where_filter(item_attrs, where_filter)? {
1375 return Ok(ModificationResult {
1377 changed: false,
1378 modified_nodes: vec![],
1379 });
1380 }
1381 }
1382
1383 let backup_node = BackupNode {
1385 node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1386 identifier: op.target_name.clone(),
1387 original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1388 location: self.span_to_location(item_span),
1389 };
1390
1391 let new_derives: Vec<String> = op.derives.iter()
1393 .filter(|d| !existing_derives.contains(&d.to_string()))
1394 .cloned()
1395 .collect();
1396
1397 if new_derives.is_empty() {
1398 return Ok(ModificationResult {
1400 changed: false,
1401 modified_nodes: vec![],
1402 });
1403 }
1404
1405 let mut all_derives = existing_derives;
1407 all_derives.extend(new_derives);
1408
1409 let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1411
1412 match &mut self.syntax_tree.items[item_index] {
1414 Item::Struct(s) => {
1415 Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1416 }
1417 Item::Enum(e) => {
1418 Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1419 }
1420 _ => unreachable!(),
1421 }
1422
1423 self.replace_formatted_item(item_index, item_span)?;
1425
1426 Ok(ModificationResult {
1427 changed: true,
1428 modified_nodes: vec![backup_node],
1429 })
1430 }
1431
1432 fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1434 let item_start_pos = self.span_to_byte_offset(original_span.start());
1436 let item_end_pos = self.span_to_byte_offset(original_span.end());
1437
1438 let mut actual_start = item_start_pos;
1440
1441 let mut temp_pos = item_start_pos;
1443 while temp_pos > 0 {
1444 temp_pos = temp_pos.saturating_sub(1);
1446 let mut line_start = temp_pos;
1447 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1448 line_start -= 1;
1449 }
1450
1451 let line = if temp_pos < self.content.len() {
1452 &self.content[line_start..temp_pos + 1]
1453 } else {
1454 &self.content[line_start..]
1455 };
1456 let trimmed = line.trim();
1457
1458 if trimmed.starts_with("#[") {
1459 actual_start = line_start;
1460 temp_pos = line_start;
1461 } else if trimmed.is_empty() {
1462 temp_pos = line_start;
1463 } else {
1464 break;
1465 }
1466
1467 if line_start == 0 {
1468 break;
1469 }
1470 }
1471
1472 let item_clone = self.syntax_tree.items[item_index].clone();
1474 let temp_file = syn::File {
1475 shebang: None,
1476 attrs: Vec::new(),
1477 items: vec![item_clone],
1478 };
1479
1480 let formatted = prettyplease::unparse(&temp_file);
1482 let formatted = formatted.trim();
1483
1484 self.content.replace_range(actual_start..item_end_pos, formatted);
1486
1487 Ok(())
1488 }
1489
1490 fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1492 for attr in attrs {
1493 if attr.path().is_ident("derive") {
1494 if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1495 let tokens_str = meta_list.tokens.to_string();
1496 return tokens_str
1497 .split(',')
1498 .map(|s| s.trim().to_string())
1499 .collect();
1500 }
1501 }
1502 }
1503 Vec::new()
1504 }
1505
1506 fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1511 if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1513 let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1514 let existing_derives = Self::extract_derives(attrs);
1515
1516 for required_trait in required_traits {
1518 if existing_derives.iter().any(|d| d == required_trait) {
1519 return Ok(true);
1520 }
1521 }
1522 return Ok(false);
1523 }
1524
1525 Ok(true)
1527 }
1528
1529 fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1531 let derive_str = derives.join(", ");
1532
1533 let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1535 let parsed: syn::ItemStruct = parse_str(&dummy)
1536 .context("Failed to parse derive attribute")?;
1537
1538 let new_attr = parsed.attrs.into_iter()
1539 .find(|a| a.path().is_ident("derive"))
1540 .context("Failed to extract derive attribute")?;
1541
1542 if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1544 attrs[pos] = new_attr;
1545 } else {
1546 attrs.insert(0, new_attr);
1548 }
1549
1550 Ok(())
1551 }
1552
1553 fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1555 if modified_function.is_none() {
1557 self.content = prettyplease::unparse(&self.syntax_tree);
1558 return Ok(());
1559 }
1560
1561 let original_syntax_tree: File = syn::parse_str(&self.content)
1563 .context("Failed to re-parse original content")?;
1564
1565 let function_name = modified_function.as_ref().unwrap();
1566
1567 let original_fn = original_syntax_tree.items.iter()
1569 .find_map(|item| {
1570 if let Item::Fn(f) = item {
1571 if f.sig.ident == function_name {
1572 return Some(f.clone());
1573 }
1574 }
1575 None
1576 })
1577 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1578
1579 let start = self.span_to_byte_offset(original_fn.span().start());
1581 let end = self.span_to_byte_offset(original_fn.span().end());
1582
1583 let modified_fn = self.syntax_tree.items.iter()
1585 .find_map(|item| {
1586 if let Item::Fn(f) = item {
1587 if f.sig.ident == function_name {
1588 return Some(f.clone());
1589 }
1590 }
1591 None
1592 })
1593 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1594
1595 let dummy_file = syn::File {
1597 shebang: None,
1598 attrs: Vec::new(),
1599 items: vec![Item::Fn(modified_fn)],
1600 };
1601
1602 let formatted_fn = prettyplease::unparse(&dummy_file);
1603
1604 let formatted_fn = formatted_fn.trim();
1606
1607 self.content.replace_range(start..end, formatted_fn);
1609
1610 Ok(())
1611 }
1612
1613 fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1614 let line_idx = pos.line.saturating_sub(1);
1615 if line_idx < self.line_offsets.len() {
1616 self.line_offsets[line_idx] + pos.column
1617 } else {
1618 self.content.len()
1619 }
1620 }
1621
1622 fn find_after_field_end(&self, pos: usize) -> usize {
1623 let mut i = pos;
1625 while i < self.content.len() {
1626 match self.content.as_bytes()[i] as char {
1627 ',' => return i + 1,
1628 '\n' => return i + 1,
1629 _ => i += 1,
1630 }
1631 }
1632 pos
1633 }
1634
1635 fn get_indentation(&self, pos: usize) -> String {
1636 let mut line_start = pos;
1638 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1639 line_start -= 1;
1640 }
1641
1642 let mut indent = String::new();
1644 let mut i = line_start;
1645 while i < self.content.len() {
1646 match self.content.as_bytes()[i] as char {
1647 ' ' | '\t' => {
1648 indent.push(self.content.as_bytes()[i] as char);
1649 i += 1;
1650 }
1651 _ => break,
1652 }
1653 }
1654
1655 if indent.is_empty() {
1657 " ".to_string()
1658 } else {
1659 indent
1660 }
1661 }
1662
1663 pub fn to_string(&self) -> String {
1664 self.content.clone()
1665 }
1666
1667 pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1669 use syn::visit::Visit;
1670 use crate::operations::InspectResult;
1671
1672 let mut results = Vec::new();
1673
1674 match node_type {
1675 "struct-literal" => {
1676 struct StructLiteralVisitor<'a> {
1678 results: &'a mut Vec<InspectResult>,
1679 name_filter: Option<&'a str>,
1680 editor: &'a RustEditor,
1681 }
1682
1683 impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1684 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1685 let filter = match self.name_filter {
1691 Some(f) => f,
1692 None => {
1693 let struct_name = node.path.segments.last()
1695 .map(|seg| seg.ident.to_string())
1696 .unwrap_or_default();
1697
1698 let snippet = self.editor.format_expr_struct(node);
1699 let location = self.editor.span_to_location(node.span());
1700
1701 self.results.push(InspectResult {
1702 file_path: String::new(),
1703 node_type: "ExprStruct".to_string(),
1704 identifier: struct_name,
1705 location,
1706 snippet,
1707 });
1708
1709 syn::visit::visit_expr_struct(self, node);
1710 return;
1711 }
1712 };
1713
1714 let matches = if filter.contains("::") {
1716 if filter.starts_with("*::") {
1718 let target_name = &filter[3..]; node.path.segments.last()
1721 .map(|seg| seg.ident.to_string() == target_name)
1722 .unwrap_or(false)
1723 } else {
1724 let path_str = node.path.segments.iter()
1726 .map(|seg| seg.ident.to_string())
1727 .collect::<Vec<_>>()
1728 .join("::");
1729 path_str == filter
1730 }
1731 } else {
1732 node.path.get_ident()
1734 .map(|ident| ident.to_string() == filter)
1735 .unwrap_or(false)
1736 };
1737
1738 if !matches {
1739 syn::visit::visit_expr_struct(self, node);
1740 return;
1741 }
1742
1743 let struct_name = node.path.segments.last()
1745 .map(|seg| seg.ident.to_string())
1746 .unwrap_or_default();
1747
1748 let snippet = self.editor.format_expr_struct(node);
1750 let location = self.editor.span_to_location(node.span());
1751
1752 self.results.push(InspectResult {
1753 file_path: String::new(), node_type: "ExprStruct".to_string(),
1755 identifier: struct_name,
1756 location,
1757 snippet,
1758 });
1759
1760 syn::visit::visit_expr_struct(self, node);
1762 }
1763 }
1764
1765 let mut visitor = StructLiteralVisitor {
1766 results: &mut results,
1767 name_filter,
1768 editor: self,
1769 };
1770
1771 for item in &self.syntax_tree.items {
1773 syn::visit::visit_item(&mut visitor, item);
1774 }
1775 }
1776 "match-arm" => {
1777 struct MatchArmVisitor<'a> {
1779 results: &'a mut Vec<InspectResult>,
1780 pattern_filter: Option<&'a str>,
1781 editor: &'a RustEditor,
1782 }
1783
1784 impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1785 fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1786 for arm in &node.arms {
1788 let pat = &arm.pat;
1790 let pattern_str = quote::quote!(#pat).to_string();
1791
1792 if let Some(filter) = self.pattern_filter {
1794 let normalized_pattern = pattern_str.replace(" ", "");
1796 let normalized_filter = filter.replace(" ", "");
1797
1798 if !normalized_pattern.contains(&normalized_filter) {
1799 continue;
1800 }
1801 }
1802
1803 let snippet = self.editor.format_match_arm(arm);
1805 let location = self.editor.span_to_location(arm.span());
1806
1807 self.results.push(InspectResult {
1808 file_path: String::new(), node_type: "MatchArm".to_string(),
1810 identifier: pattern_str.replace(" ", ""),
1811 location,
1812 snippet,
1813 });
1814 }
1815
1816 syn::visit::visit_expr_match(self, node);
1818 }
1819 }
1820
1821 let mut visitor = MatchArmVisitor {
1822 results: &mut results,
1823 pattern_filter: name_filter,
1824 editor: self,
1825 };
1826
1827 for item in &self.syntax_tree.items {
1829 syn::visit::visit_item(&mut visitor, item);
1830 }
1831 }
1832 "enum-usage" => {
1833 struct EnumUsageVisitor<'a> {
1835 results: &'a mut Vec<InspectResult>,
1836 path_filter: Option<&'a str>,
1837 editor: &'a RustEditor,
1838 }
1839
1840 impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1841 fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1842 let path = &node.path;
1844 let path_str = quote::quote!(#path).to_string();
1845
1846 if let Some(filter) = self.path_filter {
1848 let normalized_path = path_str.replace(" ", "");
1850 let normalized_filter = filter.replace(" ", "");
1851
1852 if !normalized_path.contains(&normalized_filter) {
1853 syn::visit::visit_expr_path(self, node);
1854 return;
1855 }
1856 }
1857
1858 let snippet = self.editor.format_expr_path(node);
1860 let location = self.editor.span_to_location(node.span());
1861
1862 self.results.push(InspectResult {
1863 file_path: String::new(), node_type: "ExprPath".to_string(),
1865 identifier: path_str.replace(" ", ""),
1866 location,
1867 snippet,
1868 });
1869
1870 syn::visit::visit_expr_path(self, node);
1872 }
1873 }
1874
1875 let mut visitor = EnumUsageVisitor {
1876 results: &mut results,
1877 path_filter: name_filter,
1878 editor: self,
1879 };
1880
1881 for item in &self.syntax_tree.items {
1883 syn::visit::visit_item(&mut visitor, item);
1884 }
1885 }
1886 "function-call" => {
1887 struct FunctionCallVisitor<'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 FunctionCallVisitor<'a> {
1895 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1896 let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1898 expr_path.path.segments.last()
1900 .map(|seg| seg.ident.to_string())
1901 .unwrap_or_default()
1902 } else {
1903 quote::quote!(#node.func).to_string()
1905 };
1906
1907 if let Some(filter) = self.name_filter {
1909 if func_name != filter {
1910 syn::visit::visit_expr_call(self, node);
1911 return;
1912 }
1913 }
1914
1915 let snippet = self.editor.format_expr_call(node);
1917 let location = self.editor.span_to_location(node.span());
1918
1919 self.results.push(InspectResult {
1920 file_path: String::new(), node_type: "ExprCall".to_string(),
1922 identifier: func_name,
1923 location,
1924 snippet,
1925 });
1926
1927 syn::visit::visit_expr_call(self, node);
1929 }
1930 }
1931
1932 let mut visitor = FunctionCallVisitor {
1933 results: &mut results,
1934 name_filter,
1935 editor: self,
1936 };
1937
1938 for item in &self.syntax_tree.items {
1940 syn::visit::visit_item(&mut visitor, item);
1941 }
1942 }
1943 "method-call" => {
1944 struct MethodCallVisitor<'a> {
1946 results: &'a mut Vec<InspectResult>,
1947 name_filter: Option<&'a str>,
1948 editor: &'a RustEditor,
1949 }
1950
1951 impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1952 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1953 let method_name = node.method.to_string();
1955
1956 if let Some(filter) = self.name_filter {
1958 if method_name != filter {
1959 syn::visit::visit_expr_method_call(self, node);
1960 return;
1961 }
1962 }
1963
1964 let snippet = self.editor.format_expr_method_call(node);
1966 let location = self.editor.span_to_location(node.span());
1967
1968 self.results.push(InspectResult {
1969 file_path: String::new(), node_type: "ExprMethodCall".to_string(),
1971 identifier: method_name,
1972 location,
1973 snippet,
1974 });
1975
1976 syn::visit::visit_expr_method_call(self, node);
1978 }
1979 }
1980
1981 let mut visitor = MethodCallVisitor {
1982 results: &mut results,
1983 name_filter,
1984 editor: self,
1985 };
1986
1987 for item in &self.syntax_tree.items {
1989 syn::visit::visit_item(&mut visitor, item);
1990 }
1991 }
1992 "identifier" => {
1993 struct IdentifierVisitor<'a> {
1995 results: &'a mut Vec<InspectResult>,
1996 name_filter: Option<&'a str>,
1997 editor: &'a RustEditor,
1998 }
1999
2000 impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2001 fn visit_ident(&mut self, node: &'ast syn::Ident) {
2002 let ident_name = node.to_string();
2004
2005 if let Some(filter) = self.name_filter {
2007 if ident_name != filter {
2008 syn::visit::visit_ident(self, node);
2009 return;
2010 }
2011 }
2012
2013 let snippet = self.editor.format_ident(node);
2015 let location = self.editor.span_to_location(node.span());
2016
2017 self.results.push(InspectResult {
2018 file_path: String::new(), node_type: "Ident".to_string(),
2020 identifier: ident_name,
2021 location,
2022 snippet,
2023 });
2024
2025 syn::visit::visit_ident(self, node);
2027 }
2028 }
2029
2030 let mut visitor = IdentifierVisitor {
2031 results: &mut results,
2032 name_filter,
2033 editor: self,
2034 };
2035
2036 for item in &self.syntax_tree.items {
2038 syn::visit::visit_item(&mut visitor, item);
2039 }
2040 }
2041 "type-ref" => {
2042 struct TypeRefVisitor<'a> {
2044 results: &'a mut Vec<InspectResult>,
2045 name_filter: Option<&'a str>,
2046 editor: &'a RustEditor,
2047 }
2048
2049 impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2050 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2051 let type_name = node.path.segments.last()
2053 .map(|seg| seg.ident.to_string())
2054 .unwrap_or_default();
2055
2056 if let Some(filter) = self.name_filter {
2058 if type_name != filter {
2059 syn::visit::visit_type_path(self, node);
2060 return;
2061 }
2062 }
2063
2064 let snippet = self.editor.format_type_path(node);
2066 let location = self.editor.span_to_location(node.span());
2067
2068 let path = &node.path;
2070 let path_str = quote::quote!(#path).to_string();
2071
2072 self.results.push(InspectResult {
2073 file_path: String::new(), node_type: "TypePath".to_string(),
2075 identifier: path_str.replace(" ", ""),
2076 location,
2077 snippet,
2078 });
2079
2080 syn::visit::visit_type_path(self, node);
2082 }
2083 }
2084
2085 let mut visitor = TypeRefVisitor {
2086 results: &mut results,
2087 name_filter,
2088 editor: self,
2089 };
2090
2091 for item in &self.syntax_tree.items {
2093 syn::visit::visit_item(&mut visitor, item);
2094 }
2095 }
2096 "macro-call" => {
2097 struct MacroCallVisitor<'a> {
2099 results: &'a mut Vec<InspectResult>,
2100 name_filter: Option<&'a str>,
2101 editor: &'a RustEditor,
2102 }
2103
2104 impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2105 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2106 let macro_name = node.mac.path.segments.last()
2108 .map(|seg| seg.ident.to_string())
2109 .unwrap_or_default();
2110
2111 if let Some(filter) = self.name_filter {
2113 if macro_name != filter {
2114 syn::visit::visit_expr_macro(self, node);
2115 return;
2116 }
2117 }
2118
2119 let snippet = self.editor.format_expr_macro(node);
2121 let location = self.editor.span_to_location(node.span());
2122
2123 self.results.push(InspectResult {
2124 file_path: String::new(), node_type: "ExprMacro".to_string(),
2126 identifier: macro_name,
2127 location,
2128 snippet,
2129 });
2130
2131 syn::visit::visit_expr_macro(self, node);
2133 }
2134
2135 fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2136 if let syn::Stmt::Macro(macro_stmt) = node {
2138 let macro_name = macro_stmt.mac.path.segments.last()
2139 .map(|seg| seg.ident.to_string())
2140 .unwrap_or_default();
2141
2142 if let Some(filter) = self.name_filter {
2144 if macro_name != filter {
2145 syn::visit::visit_stmt(self, node);
2146 return;
2147 }
2148 }
2149
2150 let snippet = self.editor.format_stmt_macro(macro_stmt);
2152 let location = self.editor.span_to_location(macro_stmt.span());
2153
2154 self.results.push(InspectResult {
2155 file_path: String::new(), node_type: "StmtMacro".to_string(),
2157 identifier: macro_name,
2158 location,
2159 snippet,
2160 });
2161 }
2162
2163 syn::visit::visit_stmt(self, node);
2165 }
2166 }
2167
2168 let mut visitor = MacroCallVisitor {
2169 results: &mut results,
2170 name_filter,
2171 editor: self,
2172 };
2173
2174 for item in &self.syntax_tree.items {
2176 syn::visit::visit_item(&mut visitor, item);
2177 }
2178 }
2179 _ => anyhow::bail!("Unsupported node type: {}", node_type),
2180 }
2181
2182 Ok(results)
2183 }
2184
2185 fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
2187 let start = self.span_to_byte_offset(expr.span().start());
2189 let end = self.span_to_byte_offset(expr.span().end());
2190
2191 let original = &self.content[start..end];
2193
2194 original.split_whitespace().collect::<Vec<_>>().join(" ")
2196 }
2197
2198 fn format_match_arm(&self, arm: &syn::Arm) -> String {
2200 let start = self.span_to_byte_offset(arm.span().start());
2202 let end = self.span_to_byte_offset(arm.span().end());
2203
2204 let original = &self.content[start..end];
2206
2207 original.split_whitespace().collect::<Vec<_>>().join(" ")
2209 }
2210
2211 fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2213 let start = self.span_to_byte_offset(expr.span().start());
2215 let end = self.span_to_byte_offset(expr.span().end());
2216
2217 let original = &self.content[start..end];
2219
2220 original.split_whitespace().collect::<Vec<_>>().join(" ")
2222 }
2223
2224 fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
2226 let start = self.span_to_byte_offset(expr.span().start());
2228 let end = self.span_to_byte_offset(expr.span().end());
2229
2230 let original = &self.content[start..end];
2232
2233 original.split_whitespace().collect::<Vec<_>>().join(" ")
2235 }
2236
2237 fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2239 let start = self.span_to_byte_offset(expr.span().start());
2241 let end = self.span_to_byte_offset(expr.span().end());
2242
2243 let original = &self.content[start..end];
2245
2246 original.split_whitespace().collect::<Vec<_>>().join(" ")
2248 }
2249
2250 fn format_ident(&self, ident: &syn::Ident) -> String {
2252 ident.to_string()
2253 }
2254
2255 fn format_type_path(&self, ty: &syn::TypePath) -> String {
2257 let start = self.span_to_byte_offset(ty.span().start());
2259 let end = self.span_to_byte_offset(ty.span().end());
2260
2261 let original = &self.content[start..end];
2263
2264 original.split_whitespace().collect::<Vec<_>>().join(" ")
2266 }
2267
2268 fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
2270 let start = self.span_to_byte_offset(expr.span().start());
2272 let end = self.span_to_byte_offset(expr.span().end());
2273
2274 let original = &self.content[start..end];
2276
2277 original.split_whitespace().collect::<Vec<_>>().join(" ")
2279 }
2280
2281 fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
2283 let start = self.span_to_byte_offset(stmt.span().start());
2285 let end = self.span_to_byte_offset(stmt.span().end());
2286
2287 let original = &self.content[start..end];
2289
2290 original.split_whitespace().collect::<Vec<_>>().join(" ")
2292 }
2293
2294 #[allow(dead_code)]
2296 pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2297 for (index, item) in self.syntax_tree.items.iter().enumerate() {
2298 match (node_type, item) {
2299 ("struct", Item::Struct(s)) if s.ident == name => {
2300 return Ok(index);
2301 }
2302 ("enum", Item::Enum(e)) if e.ident == name => {
2303 return Ok(index);
2304 }
2305 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2306 return Ok(index);
2307 }
2308 ("impl", Item::Impl(impl_block)) => {
2309 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2311 if let Some(segment) = type_path.path.segments.last() {
2312 if segment.ident == name {
2313 return Ok(index);
2314 }
2315 }
2316 }
2317 }
2318 _ => {}
2319 }
2320 }
2321
2322 anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2323 }
2324
2325 #[allow(dead_code)]
2327 pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2328 if index >= self.syntax_tree.items.len() {
2329 anyhow::bail!("Index {} out of bounds", index);
2330 }
2331
2332 self.syntax_tree.items[index] = new_item;
2334
2335 self.content = prettyplease::unparse(&self.syntax_tree);
2337
2338 self.line_offsets = Self::compute_line_offsets(&self.content);
2340
2341 Ok(())
2342 }
2343
2344 pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2345 let mut locations = Vec::new();
2346
2347 for item in &self.syntax_tree.items {
2348 match (node_type, item) {
2349 ("struct", Item::Struct(s)) if s.ident == name => {
2350 locations.push(self.span_to_location(s.span()));
2351 }
2352 ("enum", Item::Enum(e)) if e.ident == name => {
2353 locations.push(self.span_to_location(e.span()));
2354 }
2355 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2356 locations.push(self.span_to_location(f.span()));
2357 }
2358 _ => {}
2359 }
2360 }
2361
2362 if locations.is_empty() {
2363 anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2364 }
2365
2366 Ok(locations)
2367 }
2368
2369 fn span_to_location(&self, span: Span) -> NodeLocation {
2370 let start = span.start();
2371 let end = span.end();
2372
2373 NodeLocation {
2374 line: start.line,
2375 column: start.column,
2376 end_line: end.line,
2377 end_column: end.column,
2378 }
2379 }
2380
2381 pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
2383 use crate::operations::{InspectResult, TransformAction};
2384
2385 let matches = self.inspect(&op.node_type, op.name_filter.as_deref())?;
2387
2388 let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
2390 matches.into_iter()
2391 .filter(|m| m.snippet.contains(content_filter))
2392 .collect()
2393 } else {
2394 matches
2395 };
2396
2397 if filtered_matches.is_empty() {
2398 return Ok(ModificationResult {
2399 changed: false,
2400 modified_nodes: vec![],
2401 });
2402 }
2403
2404 let mut sorted_matches = filtered_matches;
2407 sorted_matches.sort_by(|a, b| {
2408 b.location.line.cmp(&a.location.line)
2409 .then(b.location.column.cmp(&a.location.column))
2410 });
2411
2412 let mut modified_nodes = Vec::new();
2413
2414 for match_result in &sorted_matches {
2415 let backup_node = BackupNode {
2417 node_type: match_result.node_type.clone(),
2418 identifier: match_result.identifier.clone(),
2419 original_content: match_result.snippet.clone(),
2420 location: match_result.location.clone(),
2421 };
2422
2423 let start_offset = self.line_column_to_byte_offset(
2425 match_result.location.line,
2426 match_result.location.column
2427 )?;
2428 let end_offset = self.line_column_to_byte_offset(
2429 match_result.location.end_line,
2430 match_result.location.end_column
2431 )?;
2432
2433 let original_text = &self.content[start_offset..end_offset];
2435
2436 let replacement = match &op.action {
2438 TransformAction::Comment => {
2439 format!("// {}", original_text.replace("\n", "\n// "))
2441 }
2442 TransformAction::Remove => {
2443 String::new()
2445 }
2446 TransformAction::Replace { with } => {
2447 with.clone()
2449 }
2450 };
2451
2452 self.content.replace_range(start_offset..end_offset, &replacement);
2454
2455 self.line_offsets = Self::compute_line_offsets(&self.content);
2457
2458 modified_nodes.push(backup_node);
2459 }
2460
2461 if !modified_nodes.is_empty() {
2463 }
2467
2468 Ok(ModificationResult {
2469 changed: !modified_nodes.is_empty(),
2470 modified_nodes,
2471 })
2472 }
2473
2474 pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
2476 let mut renamer = EnumVariantRenamer {
2479 enum_name: op.enum_name.clone(),
2480 old_variant: op.old_variant.clone(),
2481 new_variant: op.new_variant.clone(),
2482 modified: false,
2483 };
2484
2485 renamer.visit_file_mut(&mut self.syntax_tree);
2487
2488 if !renamer.modified {
2489 return Ok(ModificationResult {
2490 changed: false,
2491 modified_nodes: vec![],
2492 });
2493 }
2494
2495 self.content = prettyplease::unparse(&self.syntax_tree);
2497
2498 self.line_offsets = Self::compute_line_offsets(&self.content);
2500
2501 let backup_node = BackupNode {
2503 node_type: "EnumVariantRename".to_string(),
2504 identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
2505 original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
2506 location: NodeLocation {
2507 line: 1,
2508 column: 0,
2509 end_line: 1,
2510 end_column: 0,
2511 },
2512 };
2513
2514 Ok(ModificationResult {
2515 changed: true,
2516 modified_nodes: vec![backup_node],
2517 })
2518 }
2519
2520 fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
2522 if line == 0 || line > self.line_offsets.len() {
2523 anyhow::bail!("Line {} out of range", line);
2524 }
2525
2526 let line_start = self.line_offsets[line - 1];
2527 Ok(line_start + column)
2528 }
2529}
2530
2531struct MatchArmAdder {
2533 target_function: Option<String>,
2534 arm_to_add: Arm,
2535 modified: bool,
2536 current_function: Option<String>,
2537 modified_function: Option<String>,
2538}
2539
2540impl VisitMut for MatchArmAdder {
2541 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2542 let prev_fn = self.current_function.clone();
2543 self.current_function = Some(node.sig.ident.to_string());
2544
2545 syn::visit_mut::visit_item_fn_mut(self, node);
2547
2548 self.current_function = prev_fn;
2549 }
2550
2551 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2552 if let Some(ref target) = self.target_function {
2554 if self.current_function.as_ref() != Some(target) {
2555 syn::visit_mut::visit_expr_match_mut(self, node);
2557 return;
2558 }
2559 }
2560
2561 let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2563 let already_exists = node.arms.iter().any(|arm| {
2564 arm.pat.to_token_stream().to_string() == pattern_str
2565 });
2566
2567 if !already_exists {
2568 node.arms.push(self.arm_to_add.clone());
2570 self.modified = true;
2571 self.modified_function = self.current_function.clone();
2572 }
2573
2574 syn::visit_mut::visit_expr_match_mut(self, node);
2576 }
2577}
2578
2579struct MatchArmUpdater {
2581 target_function: Option<String>,
2582 pattern_to_match: String,
2583 new_body: syn::Expr,
2584 modified: bool,
2585 current_function: Option<String>,
2586 modified_function: Option<String>,
2587}
2588
2589impl VisitMut for MatchArmUpdater {
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 for arm in &mut node.arms {
2610 let pattern_str = arm.pat.to_token_stream().to_string();
2611 let pattern_normalized = pattern_str.replace(" ", "");
2613 let target_normalized = self.pattern_to_match.replace(" ", "");
2614
2615 if pattern_normalized == target_normalized {
2616 arm.body = Box::new(self.new_body.clone());
2617 self.modified = true;
2618 self.modified_function = self.current_function.clone();
2619 break;
2620 }
2621 }
2622
2623 syn::visit_mut::visit_expr_match_mut(self, node);
2624 }
2625}
2626
2627struct MatchArmRemover {
2629 target_function: Option<String>,
2630 pattern_to_remove: String,
2631 modified: bool,
2632 current_function: Option<String>,
2633 modified_function: Option<String>,
2634}
2635
2636impl VisitMut for MatchArmRemover {
2637 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2638 let prev_fn = self.current_function.clone();
2639 self.current_function = Some(node.sig.ident.to_string());
2640
2641 syn::visit_mut::visit_item_fn_mut(self, node);
2642
2643 self.current_function = prev_fn;
2644 }
2645
2646 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2647 if let Some(ref target) = self.target_function {
2649 if self.current_function.as_ref() != Some(target) {
2650 syn::visit_mut::visit_expr_match_mut(self, node);
2651 return;
2652 }
2653 }
2654
2655 let mut index_to_remove = None;
2657 for (i, arm) in node.arms.iter().enumerate() {
2658 let pattern_str = arm.pat.to_token_stream().to_string();
2659 let pattern_normalized = pattern_str.replace(" ", "");
2661 let target_normalized = self.pattern_to_remove.replace(" ", "");
2662
2663 if pattern_normalized == target_normalized {
2664 index_to_remove = Some(i);
2665 break;
2666 }
2667 }
2668
2669 if let Some(index) = index_to_remove {
2670 node.arms.remove(index);
2671 self.modified = true;
2672 self.modified_function = self.current_function.clone();
2673 }
2674
2675 syn::visit_mut::visit_expr_match_mut(self, node);
2676 }
2677}
2678
2679struct MultiMatchArmAdder {
2681 target_function: Option<String>,
2682 arms_to_add: Vec<(String, Arm)>, modified: bool,
2684 current_function: Option<String>,
2685 modified_function: Option<String>,
2686}
2687
2688impl VisitMut for MultiMatchArmAdder {
2689 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2690 let prev_fn = self.current_function.clone();
2691 self.current_function = Some(node.sig.ident.to_string());
2692
2693 syn::visit_mut::visit_item_fn_mut(self, node);
2694
2695 self.current_function = prev_fn;
2696 }
2697
2698 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2699 if let Some(ref target) = self.target_function {
2701 if self.current_function.as_ref() != Some(target) {
2702 syn::visit_mut::visit_expr_match_mut(self, node);
2703 return;
2704 }
2705 }
2706
2707 for (pattern_str, arm) in &self.arms_to_add {
2709 let already_exists = node.arms.iter().any(|existing_arm| {
2711 existing_arm.pat.to_token_stream().to_string() == *pattern_str
2712 });
2713
2714 if !already_exists {
2715 node.arms.push(arm.clone());
2716 self.modified = true;
2717 self.modified_function = self.current_function.clone();
2718 }
2719 }
2720
2721 syn::visit_mut::visit_expr_match_mut(self, node);
2722 }
2723}
2724
2725struct StructLiteralFieldAdder {
2727 struct_name: String,
2728 field_def: String,
2729 field_name: String,
2730 position: InsertPosition,
2731 modified: bool,
2732}
2733
2734impl VisitMut for StructLiteralFieldAdder {
2735 fn visit_expr_mut(&mut self, node: &mut Expr) {
2736 if let Expr::Struct(expr_struct) = node {
2738 let is_match = if self.struct_name.contains("::") {
2744 if self.struct_name.starts_with("*::") {
2746 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
2749 .map(|seg| seg.ident.to_string() == target_name)
2750 .unwrap_or(false)
2751 } else {
2752 let path_str = expr_struct.path.segments.iter()
2754 .map(|seg| seg.ident.to_string())
2755 .collect::<Vec<_>>()
2756 .join("::");
2757 path_str == self.struct_name
2758 }
2759 } else {
2760 expr_struct.path.segments.len() == 1
2762 && expr_struct.path.segments.last()
2763 .map(|seg| seg.ident.to_string())
2764 .as_ref() == Some(&self.struct_name)
2765 };
2766
2767 if is_match {
2768 let field_exists = expr_struct.fields.iter().any(|fv| {
2770 fv.member.to_token_stream().to_string() == self.field_name
2771 });
2772
2773 if !field_exists {
2774 let field_value_code = format!("{{ {} }}", self.field_def);
2777 if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
2778 if let Some(new_fv) = expr.fields.first() {
2779 match &self.position {
2781 InsertPosition::First => {
2782 expr_struct.fields.insert(0, new_fv.clone());
2783 self.modified = true;
2784 }
2785 InsertPosition::Last => {
2786 expr_struct.fields.push(new_fv.clone());
2787 self.modified = true;
2788 }
2789 InsertPosition::After(after_field) => {
2790 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2792 fv.member.to_token_stream().to_string() == *after_field
2793 }) {
2794 expr_struct.fields.insert(pos + 1, new_fv.clone());
2795 self.modified = true;
2796 }
2797 }
2798 InsertPosition::Before(before_field) => {
2799 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2801 fv.member.to_token_stream().to_string() == *before_field
2802 }) {
2803 expr_struct.fields.insert(pos, new_fv.clone());
2804 self.modified = true;
2805 }
2806 }
2807 }
2808 }
2809 }
2810 }
2811 }
2812 }
2813
2814 syn::visit_mut::visit_expr_mut(self, node);
2817 }
2818}
2819
2820struct EnumVariantRenamer {
2822 enum_name: String,
2823 old_variant: String,
2824 new_variant: String,
2825 modified: bool,
2826}
2827
2828impl EnumVariantRenamer {
2829 fn rename_path(&mut self, path: &mut syn::Path) {
2831 if path.segments.len() == 2 {
2832 if path.segments[0].ident == self.enum_name
2833 && path.segments[1].ident == self.old_variant
2834 {
2835 path.segments[1].ident = syn::Ident::new(&self.new_variant, path.segments[1].ident.span());
2836 self.modified = true;
2837 }
2838 } else if path.segments.len() == 1 {
2839 if path.segments[0].ident == self.old_variant {
2840 path.segments[0].ident = syn::Ident::new(&self.new_variant, path.segments[0].ident.span());
2841 self.modified = true;
2842 }
2843 }
2844 }
2845}
2846
2847impl VisitMut for EnumVariantRenamer {
2848 fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
2850 if node.ident == self.enum_name {
2851 for variant in &mut node.variants {
2852 if variant.ident == self.old_variant {
2853 variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
2854 self.modified = true;
2855 }
2856 }
2857 }
2858
2859 syn::visit_mut::visit_item_enum_mut(self, node);
2861 }
2862
2863 fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
2865 match pat {
2866 syn::Pat::TupleStruct(tuple_struct) => {
2867 self.rename_path(&mut tuple_struct.path);
2868 }
2869 syn::Pat::Struct(struct_pat) => {
2870 self.rename_path(&mut struct_pat.path);
2871 }
2872 syn::Pat::Path(path_pat) => {
2873 self.rename_path(&mut path_pat.path);
2874 }
2875 _ => {}
2876 }
2877
2878 syn::visit_mut::visit_pat_mut(self, pat);
2880 }
2881
2882 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
2884 match expr {
2885 syn::Expr::Path(expr_path) => {
2886 self.rename_path(&mut expr_path.path);
2887 }
2888 syn::Expr::Call(call) => {
2889 if let syn::Expr::Path(path) = &mut *call.func {
2890 self.rename_path(&mut path.path);
2891 }
2892 }
2893 syn::Expr::Struct(struct_expr) => {
2894 self.rename_path(&mut struct_expr.path);
2895 }
2896 _ => {}
2897 }
2898
2899 syn::visit_mut::visit_expr_mut(self, expr);
2901 }
2902}