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