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 }
84 }
85
86 pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
87 let item_struct = self.syntax_tree.items.iter()
89 .find_map(|item| {
90 if let Item::Struct(s) = item {
91 if s.ident == op.struct_name {
92 return Some(s.clone());
93 }
94 }
95 None
96 })
97 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
98
99 if let Some(ref where_filter) = op.where_filter {
101 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
102 return Ok(ModificationResult {
104 changed: false,
105 modified_nodes: vec![],
106 });
107 }
108 }
109
110 let backup_node = BackupNode {
112 node_type: "ItemStruct".to_string(),
113 identifier: op.struct_name.clone(),
114 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
115 location: self.span_to_location(item_struct.span()),
116 };
117
118 let modified = self.insert_struct_field(&item_struct, op)
120 .context("Failed to add field to struct definition")?;
121
122 if !modified {
123 return Ok(ModificationResult {
124 changed: false,
125 modified_nodes: vec![],
126 });
127 }
128
129 let mut modified_nodes = vec![backup_node];
130
131 if let Some(ref literal_default) = op.literal_default {
133 self.syntax_tree = syn::parse_str(&self.content)
135 .context("Failed to re-parse content after adding struct field")?;
136 self.line_offsets = Self::compute_line_offsets(&self.content);
137
138 let field_name = op.field_def.split(':')
140 .next()
141 .map(|s| s.trim().to_string())
142 .context("Failed to extract field name from field definition")?;
143
144 let literal_op = AddStructLiteralFieldOp {
146 struct_name: op.struct_name.clone(),
147 field_def: format!("{}: {}", field_name, literal_default),
148 position: op.position.clone(),
149 };
150
151 let literal_result = self.add_struct_literal_field(&literal_op)
153 .context("Failed to update struct literals")?;
154 modified_nodes.extend(literal_result.modified_nodes);
155 }
156
157 Ok(ModificationResult {
158 changed: true,
159 modified_nodes,
160 })
161 }
162
163 fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
164 if let Fields::Named(ref fields) = item_struct.fields {
165 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
167 let dummy: ItemStruct = parse_str(&field_code)
168 .context("Failed to parse field definition")?;
169
170 let new_field = if let Fields::Named(ref nf) = dummy.fields {
171 nf.named.first()
172 .context("No field found in definition")?
173 .clone()
174 } else {
175 anyhow::bail!("Expected named field");
176 };
177
178 let new_field_name = new_field.ident.as_ref()
180 .map(|i| i.to_string())
181 .context("Field must have a name")?;
182
183 if fields.named.iter().any(|f| {
184 f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
185 }) {
186 return Ok(false);
188 }
189
190 let insert_pos = match &op.position {
192 InsertPosition::First => {
193 if let Some(first_field) = fields.named.first() {
194 self.span_to_byte_offset(first_field.span().start())
195 } else {
196 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
198 brace_pos + 1
199 }
200 }
201 InsertPosition::Last => {
202 if let Some(last_field) = fields.named.last() {
203 let end = self.span_to_byte_offset(last_field.span().end());
204 self.find_after_field_end(end)
206 } else {
207 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
209 brace_pos + 1
210 }
211 }
212 InsertPosition::After(name) => {
213 let field = fields.named.iter()
214 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
215 .with_context(|| format!("Field '{}' not found", name))?;
216 let end = self.span_to_byte_offset(field.span().end());
217 self.find_after_field_end(end)
218 }
219 InsertPosition::Before(name) => {
220 let field = fields.named.iter()
221 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
222 .with_context(|| format!("Field '{}' not found", name))?;
223 self.span_to_byte_offset(field.span().start())
224 }
225 };
226
227 let indent = self.get_indentation(insert_pos);
229 let field_str = Self::format_field(&new_field);
230 let insert_text = if matches!(op.position, InsertPosition::First) {
231 format!("\n{}{},", indent, field_str)
232 } else {
233 format!("\n{}{},", indent, field_str)
234 };
235
236 self.content.insert_str(insert_pos, &insert_text);
237 return Ok(true);
238 }
239
240 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
241 }
242
243 pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
244 let item_struct = self.syntax_tree.items.iter()
246 .find_map(|item| {
247 if let Item::Struct(s) = item {
248 if s.ident == op.struct_name {
249 return Some(s.clone());
250 }
251 }
252 None
253 })
254 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
255
256 if let Some(ref where_filter) = op.where_filter {
258 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
259 return Ok(ModificationResult {
261 changed: false,
262 modified_nodes: vec![],
263 });
264 }
265 }
266
267 let backup_node = BackupNode {
269 node_type: "ItemStruct".to_string(),
270 identifier: op.struct_name.clone(),
271 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
272 location: self.span_to_location(item_struct.span()),
273 };
274
275 let modified = self.replace_struct_field(&item_struct, op)?;
276
277 Ok(ModificationResult {
278 changed: modified,
279 modified_nodes: if modified { vec![backup_node] } else { vec![] },
280 })
281 }
282
283 fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
284 if let Fields::Named(ref fields) = item_struct.fields {
285 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
287 let dummy: ItemStruct = parse_str(&field_code)
288 .context("Failed to parse field definition")?;
289
290 let new_field = if let Fields::Named(ref nf) = dummy.fields {
291 nf.named.first()
292 .context("No field found in definition")?
293 .clone()
294 } else {
295 anyhow::bail!("Expected named field");
296 };
297
298 let field_name = new_field.ident.as_ref()
300 .map(|i| i.to_string())
301 .context("Field must have a name")?;
302
303 let existing_field = fields.named.iter()
305 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
306 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
307
308 let start = self.span_to_byte_offset(existing_field.span().start());
310 let end = self.span_to_byte_offset(existing_field.span().end());
311
312 let new_field_str = Self::format_field(&new_field);
314
315 self.content.replace_range(start..end, &new_field_str);
317
318 return Ok(true);
319 }
320
321 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
322 }
323
324 pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
325 let item_struct = self.syntax_tree.items.iter()
327 .find_map(|item| {
328 if let Item::Struct(s) = item {
329 if s.ident == op.struct_name {
330 return Some(s.clone());
331 }
332 }
333 None
334 })
335 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
336
337 if let Some(ref where_filter) = op.where_filter {
339 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
340 return Ok(ModificationResult {
342 changed: false,
343 modified_nodes: vec![],
344 });
345 }
346 }
347
348 let backup_node = BackupNode {
350 node_type: "ItemStruct".to_string(),
351 identifier: op.struct_name.clone(),
352 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
353 location: self.span_to_location(item_struct.span()),
354 };
355
356 if let Fields::Named(ref fields) = item_struct.fields {
357 let field_to_remove = fields.named.iter()
359 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
360 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
361
362 let start = self.span_to_byte_offset(field_to_remove.span().start());
364 let mut end = self.span_to_byte_offset(field_to_remove.span().end());
365
366 while end < self.content.len() {
368 match self.content.as_bytes()[end] as char {
369 ',' => {
370 end += 1;
371 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
373 end += 1;
374 }
375 break;
376 }
377 ' ' | '\t' => end += 1,
378 '\n' => {
379 end += 1;
380 break;
381 }
382 _ => break,
383 }
384 }
385
386 let mut line_start = start;
388 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
389 line_start -= 1;
390 }
391
392 let before_field = &self.content[line_start..start];
394 if before_field.trim().is_empty() {
395 self.content.replace_range(line_start..end, "");
397 } else {
398 self.content.replace_range(start..end, "");
400 }
401
402 return Ok(ModificationResult {
403 changed: true,
404 modified_nodes: vec![backup_node],
405 });
406 }
407
408 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
409 }
410
411 pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
412 let field_name = op.field_def.split(':')
414 .next()
415 .map(|s| s.trim().to_string())
416 .context("Field definition must contain ':'")?;
417
418 let backup_nodes = self.collect_struct_literal_backups(&op.struct_name);
420
421 let mut visitor = StructLiteralFieldAdder {
423 struct_name: op.struct_name.clone(),
424 field_def: op.field_def.clone(),
425 field_name,
426 position: op.position.clone(),
427 modified: false,
428 };
429
430 visitor.visit_file_mut(&mut self.syntax_tree);
431
432 if visitor.modified {
433 self.content = prettyplease::unparse(&self.syntax_tree);
435 Ok(ModificationResult {
436 changed: true,
437 modified_nodes: backup_nodes,
438 })
439 } else {
440 Ok(ModificationResult {
441 changed: false,
442 modified_nodes: vec![],
443 })
444 }
445 }
446
447 fn collect_struct_literal_backups(&self, struct_name: &str) -> Vec<BackupNode> {
449 use syn::visit::Visit;
450
451 struct LiteralCollector {
452 struct_name: String,
453 backups: Vec<BackupNode>,
454 counter: usize,
455 }
456
457 impl<'ast> Visit<'ast> for LiteralCollector {
458 fn visit_expr(&mut self, node: &'ast Expr) {
459 if let Expr::Struct(expr_struct) = node {
460 if let Some(last_seg) = expr_struct.path.segments.last() {
461 if last_seg.ident.to_string() == self.struct_name {
462 self.backups.push(BackupNode {
463 node_type: "ExprStruct".to_string(),
464 identifier: format!("{}#{}", self.struct_name, self.counter),
465 original_content: expr_struct.to_token_stream().to_string(),
466 location: NodeLocation {
467 line: 0, column: 0,
469 end_line: 0,
470 end_column: 0,
471 },
472 });
473 self.counter += 1;
474 }
475 }
476 }
477 syn::visit::visit_expr(self, node);
478 }
479 }
480
481 let mut collector = LiteralCollector {
482 struct_name: struct_name.to_string(),
483 backups: Vec::new(),
484 counter: 0,
485 };
486
487 collector.visit_file(&self.syntax_tree);
488 collector.backups
489 }
490
491 pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
492 let item_enum = self.syntax_tree.items.iter()
494 .find_map(|item| {
495 if let Item::Enum(e) = item {
496 if e.ident == op.enum_name {
497 return Some(e.clone());
498 }
499 }
500 None
501 })
502 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
503
504 if let Some(ref where_filter) = op.where_filter {
506 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
507 return Ok(ModificationResult {
509 changed: false,
510 modified_nodes: vec![],
511 });
512 }
513 }
514
515 let backup_node = BackupNode {
517 node_type: "ItemEnum".to_string(),
518 identifier: op.enum_name.clone(),
519 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
520 location: self.span_to_location(item_enum.span()),
521 };
522
523 let modified = self.insert_enum_variant(&item_enum, op)?;
524
525 Ok(ModificationResult {
526 changed: modified,
527 modified_nodes: if modified { vec![backup_node] } else { vec![] },
528 })
529 }
530
531 fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
532 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
534 let dummy: ItemEnum = parse_str(&variant_code)
535 .context("Failed to parse variant definition")?;
536
537 let new_variant = dummy.variants.first()
538 .context("No variant found in definition")?
539 .clone();
540
541 let variant_name = new_variant.ident.to_string();
543 if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
544 return Ok(false);
546 }
547
548 let insert_pos = match &op.position {
550 InsertPosition::First => {
551 if let Some(first_var) = item_enum.variants.first() {
552 self.span_to_byte_offset(first_var.span().start())
553 } else {
554 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
555 brace_pos + 1
556 }
557 }
558 InsertPosition::Last => {
559 if let Some(last_var) = item_enum.variants.last() {
560 let end = self.span_to_byte_offset(last_var.span().end());
561 self.find_after_field_end(end)
562 } else {
563 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
564 brace_pos + 1
565 }
566 }
567 InsertPosition::After(name) => {
568 let variant = item_enum.variants.iter()
569 .find(|v| v.ident.to_string() == *name)
570 .with_context(|| format!("Variant '{}' not found", name))?;
571 let end = self.span_to_byte_offset(variant.span().end());
572 self.find_after_field_end(end)
573 }
574 InsertPosition::Before(name) => {
575 let variant = item_enum.variants.iter()
576 .find(|v| v.ident.to_string() == *name)
577 .with_context(|| format!("Variant '{}' not found", name))?;
578 self.span_to_byte_offset(variant.span().start())
579 }
580 };
581
582 let indent = self.get_indentation(insert_pos);
583 let variant_str = new_variant.to_token_stream().to_string();
584 let insert_text = format!("\n{}{},", indent, variant_str);
585
586 self.content.insert_str(insert_pos, &insert_text);
587 Ok(true)
588 }
589
590 fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
591 let item_enum = self.syntax_tree.items.iter()
593 .find_map(|item| {
594 if let Item::Enum(e) = item {
595 if e.ident == op.enum_name {
596 return Some(e.clone());
597 }
598 }
599 None
600 })
601 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
602
603 if let Some(ref where_filter) = op.where_filter {
605 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
606 return Ok(ModificationResult {
608 changed: false,
609 modified_nodes: vec![],
610 });
611 }
612 }
613
614 let backup_node = BackupNode {
616 node_type: "ItemEnum".to_string(),
617 identifier: op.enum_name.clone(),
618 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
619 location: self.span_to_location(item_enum.span()),
620 };
621
622 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
624 let dummy: ItemEnum = parse_str(&variant_code)
625 .context("Failed to parse variant definition")?;
626
627 let new_variant = dummy.variants.first()
628 .context("No variant found in definition")?
629 .clone();
630
631 let variant_name = new_variant.ident.to_string();
632
633 let existing_variant = item_enum.variants.iter()
635 .find(|v| v.ident.to_string() == variant_name)
636 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
637
638 let start = self.span_to_byte_offset(existing_variant.span().start());
640 let end = self.span_to_byte_offset(existing_variant.span().end());
641
642 let variant_str = new_variant.to_token_stream().to_string();
644 self.content.replace_range(start..end, &variant_str);
645
646 Ok(ModificationResult {
647 changed: true,
648 modified_nodes: vec![backup_node],
649 })
650 }
651
652 pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
653 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_to_remove = item_enum.variants.iter()
686 .find(|v| v.ident.to_string() == op.variant_name)
687 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
688
689 let start = self.span_to_byte_offset(variant_to_remove.span().start());
691 let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
692
693 while end < self.content.len() {
695 match self.content.as_bytes()[end] as char {
696 ',' => {
697 end += 1;
698 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
699 end += 1;
700 }
701 break;
702 }
703 ' ' | '\t' => end += 1,
704 '\n' => {
705 end += 1;
706 break;
707 }
708 _ => break,
709 }
710 }
711
712 let mut line_start = start;
714 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
715 line_start -= 1;
716 }
717
718 let before_variant = &self.content[line_start..start];
719 if before_variant.trim().is_empty() {
720 self.content.replace_range(line_start..end, "");
721 } else {
722 self.content.replace_range(start..end, "");
723 }
724
725 Ok(ModificationResult {
726 changed: true,
727 modified_nodes: vec![backup_node],
728 })
729 }
730
731 pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
732 if op.auto_detect {
733 self.add_missing_match_arms(op)
735 } else {
736 self.add_single_match_arm(op)
738 }
739 }
740
741 fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
742 let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
744 let expr: syn::Expr = parse_str(&dummy_match)
745 .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
746
747 let arm = if let syn::Expr::Match(match_expr) = expr {
749 match_expr.arms.into_iter().next()
750 .context("Failed to extract arm from dummy match")?
751 } else {
752 anyhow::bail!("Expected match expression");
753 };
754
755 let backup_node = if let Some(ref fn_name) = op.function_name {
757 self.get_function_backup(fn_name)?
758 } else {
759 BackupNode {
762 node_type: "Unknown".to_string(),
763 identifier: "match_expression".to_string(),
764 original_content: String::new(),
765 location: NodeLocation {
766 line: 0,
767 column: 0,
768 end_line: 0,
769 end_column: 0,
770 },
771 }
772 };
773
774 let mut visitor = MatchArmAdder {
776 target_function: op.function_name.clone(),
777 arm_to_add: arm,
778 modified: false,
779 current_function: None,
780 modified_function: None,
781 };
782
783 visitor.visit_file_mut(&mut self.syntax_tree);
784
785 if visitor.modified {
786 self.replace_modified_functions(&visitor.modified_function)?;
788 Ok(ModificationResult {
789 changed: true,
790 modified_nodes: vec![backup_node],
791 })
792 } else {
793 Ok(ModificationResult {
794 changed: false,
795 modified_nodes: vec![],
796 })
797 }
798 }
799
800 fn unparse_item(&self, item: &Item) -> String {
802 let temp_file = syn::File {
803 shebang: None,
804 attrs: Vec::new(),
805 items: vec![item.clone()],
806 };
807 prettyplease::unparse(&temp_file).trim().to_string()
808 }
809
810 fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
812 for item in &self.syntax_tree.items {
813 if let Item::Fn(f) = item {
814 if f.sig.ident == fn_name {
815 return Ok(BackupNode {
816 node_type: "ItemFn".to_string(),
817 identifier: fn_name.to_string(),
818 original_content: self.unparse_item(&Item::Fn(f.clone())),
819 location: self.span_to_location(f.span()),
820 });
821 }
822 }
823 }
824 anyhow::bail!("Function '{}' not found", fn_name)
825 }
826
827 fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
828 let enum_name = op.enum_name.as_ref()
830 .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
831
832 let enum_variants = self.find_enum_variants(enum_name)?;
834
835 if enum_variants.is_empty() {
836 anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
837 }
838
839 let existing_patterns = self.find_existing_match_patterns(&op.function_name);
841
842 let mut missing_variants = Vec::new();
844 for variant in &enum_variants {
845 let pattern = format!("{}::{}", enum_name, variant);
846 let pattern_normalized = pattern.replace(" ", "");
847
848 let exists = existing_patterns.iter().any(|p| {
849 p.replace(" ", "") == pattern_normalized
850 });
851
852 if !exists {
853 missing_variants.push(variant.clone());
854 }
855 }
856
857 if missing_variants.is_empty() {
858 println!("All enum variants already covered in match expressions");
859 return Ok(ModificationResult {
860 changed: false,
861 modified_nodes: vec![],
862 });
863 }
864
865 let backup_node = if let Some(ref fn_name) = op.function_name {
867 self.get_function_backup(fn_name)?
868 } else {
869 BackupNode {
870 node_type: "Unknown".to_string(),
871 identifier: "match_expression".to_string(),
872 original_content: String::new(),
873 location: NodeLocation {
874 line: 0,
875 column: 0,
876 end_line: 0,
877 end_column: 0,
878 },
879 }
880 };
881
882 let mut arms_to_add = Vec::new();
884 for variant in &missing_variants {
885 let pattern = format!("{}::{}", enum_name, variant);
886 let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
887 let expr: syn::Expr = parse_str(&dummy_match)
888 .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
889
890 if let syn::Expr::Match(match_expr) = expr {
891 if let Some(arm) = match_expr.arms.into_iter().next() {
892 arms_to_add.push((pattern.clone(), arm));
893 }
894 }
895 }
896
897 let mut visitor = MultiMatchArmAdder {
899 target_function: op.function_name.clone(),
900 arms_to_add,
901 modified: false,
902 current_function: None,
903 modified_function: None,
904 };
905
906 visitor.visit_file_mut(&mut self.syntax_tree);
907
908 if visitor.modified {
909 for variant in &missing_variants {
911 println!("Added match arm for: {}::{}", enum_name, variant);
912 }
913
914 self.replace_modified_functions(&visitor.modified_function)?;
916 Ok(ModificationResult {
917 changed: true,
918 modified_nodes: vec![backup_node],
919 })
920 } else {
921 Ok(ModificationResult {
922 changed: false,
923 modified_nodes: vec![],
924 })
925 }
926 }
927
928 fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
929 for item in &self.syntax_tree.items {
931 if let Item::Enum(e) = item {
932 if e.ident == enum_name {
933 let variants: Vec<String> = e.variants.iter()
934 .map(|v| v.ident.to_string())
935 .collect();
936 return Ok(variants);
937 }
938 }
939 }
940
941 Ok(Vec::new())
942 }
943
944 fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
945 use syn::visit::Visit;
946
947 struct PatternCollector {
948 target_function: Option<String>,
949 current_function: Option<String>,
950 patterns: Vec<String>,
951 }
952
953 impl<'ast> Visit<'ast> for PatternCollector {
954 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
955 let prev_fn = self.current_function.clone();
956 self.current_function = Some(node.sig.ident.to_string());
957 syn::visit::visit_item_fn(self, node);
958 self.current_function = prev_fn;
959 }
960
961 fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
962 if let Some(ref target) = self.target_function {
964 if self.current_function.as_ref() != Some(target) {
965 syn::visit::visit_expr_match(self, node);
966 return;
967 }
968 }
969
970 for arm in &node.arms {
972 self.patterns.push(arm.pat.to_token_stream().to_string());
973 }
974
975 syn::visit::visit_expr_match(self, node);
976 }
977 }
978
979 let mut collector = PatternCollector {
980 target_function: function_name.clone(),
981 current_function: None,
982 patterns: Vec::new(),
983 };
984
985 collector.visit_file(&self.syntax_tree);
986 collector.patterns
987 }
988
989 pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
990 let backup_node = if let Some(ref fn_name) = op.function_name {
992 self.get_function_backup(fn_name)?
993 } else {
994 BackupNode {
995 node_type: "Unknown".to_string(),
996 identifier: "match_expression".to_string(),
997 original_content: String::new(),
998 location: NodeLocation {
999 line: 0,
1000 column: 0,
1001 end_line: 0,
1002 end_column: 0,
1003 },
1004 }
1005 };
1006
1007 let new_body: syn::Expr = parse_str(&op.new_body)
1009 .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1010
1011 let mut visitor = MatchArmUpdater {
1013 target_function: op.function_name.clone(),
1014 pattern_to_match: op.pattern.clone(),
1015 new_body,
1016 modified: false,
1017 current_function: None,
1018 modified_function: None,
1019 };
1020
1021 visitor.visit_file_mut(&mut self.syntax_tree);
1022
1023 if visitor.modified {
1024 self.replace_modified_functions(&visitor.modified_function)?;
1026 Ok(ModificationResult {
1027 changed: true,
1028 modified_nodes: vec![backup_node],
1029 })
1030 } else {
1031 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1032 }
1033 }
1034
1035 pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1036 let backup_node = if let Some(ref fn_name) = op.function_name {
1038 self.get_function_backup(fn_name)?
1039 } else {
1040 BackupNode {
1041 node_type: "Unknown".to_string(),
1042 identifier: "match_expression".to_string(),
1043 original_content: String::new(),
1044 location: NodeLocation {
1045 line: 0,
1046 column: 0,
1047 end_line: 0,
1048 end_column: 0,
1049 },
1050 }
1051 };
1052
1053 let mut visitor = MatchArmRemover {
1055 target_function: op.function_name.clone(),
1056 pattern_to_remove: op.pattern.clone(),
1057 modified: false,
1058 current_function: None,
1059 modified_function: None,
1060 };
1061
1062 visitor.visit_file_mut(&mut self.syntax_tree);
1063
1064 if visitor.modified {
1065 self.replace_modified_functions(&visitor.modified_function)?;
1067 Ok(ModificationResult {
1068 changed: true,
1069 modified_nodes: vec![backup_node],
1070 })
1071 } else {
1072 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1073 }
1074 }
1075
1076 pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1077 let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1079 let dummy: syn::ItemImpl = parse_str(&method_code)
1080 .context("Failed to parse method definition")?;
1081
1082 let new_method = dummy.items.first()
1083 .context("No method found in definition")?
1084 .clone();
1085
1086 let method_name = match &new_method {
1088 syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1089 _ => anyhow::bail!("Only method definitions are supported"),
1090 };
1091
1092 let impl_index = self.syntax_tree.items.iter().position(|item| {
1094 if let Item::Impl(impl_block) = item {
1095 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1097 if let Some(segment) = type_path.path.segments.last() {
1098 return segment.ident == op.target;
1099 }
1100 }
1101 }
1102 false
1103 }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1104
1105 let impl_block = match &self.syntax_tree.items[impl_index] {
1107 Item::Impl(i) => i,
1108 _ => unreachable!(),
1109 };
1110
1111 let method_exists = impl_block.items.iter().any(|item| {
1112 if let syn::ImplItem::Fn(f) = item {
1113 f.sig.ident == method_name
1114 } else {
1115 false
1116 }
1117 });
1118
1119 if method_exists {
1120 return Ok(ModificationResult {
1121 changed: false,
1122 modified_nodes: vec![],
1123 });
1124 }
1125
1126 let backup_node = BackupNode {
1128 node_type: "ItemImpl".to_string(),
1129 identifier: op.target.clone(),
1130 original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1131 location: self.span_to_location(impl_block.span()),
1132 };
1133
1134 let impl_span = impl_block.span();
1136
1137 match &mut self.syntax_tree.items[impl_index] {
1139 Item::Impl(impl_block) => {
1140 match &op.position {
1142 InsertPosition::First => {
1143 impl_block.items.insert(0, new_method);
1144 }
1145 InsertPosition::Last => {
1146 impl_block.items.push(new_method);
1147 }
1148 InsertPosition::After(name) => {
1149 let pos = impl_block.items.iter().position(|item| {
1150 if let syn::ImplItem::Fn(f) = item {
1151 f.sig.ident == name
1152 } else {
1153 false
1154 }
1155 }).with_context(|| format!("Method '{}' not found", name))?;
1156 impl_block.items.insert(pos + 1, new_method);
1157 }
1158 InsertPosition::Before(name) => {
1159 let pos = impl_block.items.iter().position(|item| {
1160 if let syn::ImplItem::Fn(f) = item {
1161 f.sig.ident == name
1162 } else {
1163 false
1164 }
1165 }).with_context(|| format!("Method '{}' not found", name))?;
1166 impl_block.items.insert(pos, new_method);
1167 }
1168 }
1169 }
1170 _ => unreachable!(),
1171 }
1172
1173 self.replace_formatted_item(impl_index, impl_span)?;
1175
1176 Ok(ModificationResult {
1177 changed: true,
1178 modified_nodes: vec![backup_node],
1179 })
1180 }
1181
1182 pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1183 let use_code = format!("use {};", op.use_path);
1185 let use_item: syn::ItemUse = parse_str(&use_code)
1186 .context("Failed to parse use statement")?;
1187
1188 let use_exists = self.syntax_tree.items.iter().any(|item| {
1190 if let Item::Use(existing_use) = item {
1191 existing_use.tree.to_token_stream().to_string() ==
1193 use_item.tree.to_token_stream().to_string()
1194 } else {
1195 false
1196 }
1197 });
1198
1199 if use_exists {
1200 return Ok(ModificationResult {
1201 changed: false,
1202 modified_nodes: vec![],
1203 });
1204 }
1205
1206 let backup_node = BackupNode {
1208 node_type: "ItemUse".to_string(),
1209 identifier: op.use_path.clone(),
1210 original_content: format!("use {};", op.use_path),
1211 location: NodeLocation {
1212 line: 0,
1213 column: 0,
1214 end_line: 0,
1215 end_column: 0,
1216 },
1217 };
1218
1219 let insert_index = match &op.position {
1221 InsertPosition::First => 0,
1222 InsertPosition::Last => {
1223 self.syntax_tree.items.iter()
1225 .rposition(|item| matches!(item, Item::Use(_)))
1226 .map(|i| i + 1)
1227 .unwrap_or(0)
1228 }
1229 InsertPosition::After(path) => {
1230 let pos = self.syntax_tree.items.iter().position(|item| {
1232 if let Item::Use(u) = item {
1233 u.tree.to_token_stream().to_string().contains(path)
1234 } else {
1235 false
1236 }
1237 }).with_context(|| format!("Use statement for '{}' not found", path))?;
1238 pos + 1
1239 }
1240 InsertPosition::Before(path) => {
1241 self.syntax_tree.items.iter().position(|item| {
1243 if let Item::Use(u) = item {
1244 u.tree.to_token_stream().to_string().contains(path)
1245 } else {
1246 false
1247 }
1248 }).with_context(|| format!("Use statement for '{}' not found", path))?
1249 }
1250 };
1251
1252 self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1254
1255 let insert_line_pos = if insert_index == 0 {
1258 0
1260 } else {
1261 let prev_item = &self.syntax_tree.items[insert_index - 1];
1263 let span = prev_item.span();
1264 let end_pos = self.span_to_byte_offset(span.end());
1265
1266 let mut line_end = end_pos;
1268 while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1269 line_end += 1;
1270 }
1271 if line_end < self.content.len() {
1273 line_end + 1
1274 } else {
1275 self.content.push('\n');
1277 self.content.len()
1278 }
1279 };
1280
1281 let use_str = format!("use {};\n", op.use_path);
1283
1284 self.content.insert_str(insert_line_pos, &use_str);
1286
1287 Ok(ModificationResult {
1288 changed: true,
1289 modified_nodes: vec![backup_node],
1290 })
1291 }
1292
1293 pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1294 let item_index = self.syntax_tree.items.iter().position(|item| {
1296 match (&op.target_type as &str, item) {
1297 ("struct", Item::Struct(s)) => s.ident == op.target_name,
1298 ("enum", Item::Enum(e)) => e.ident == op.target_name,
1299 _ => false,
1300 }
1301 }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1302
1303 let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1305 Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1306 Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1307 _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1308 };
1309
1310 if let Some(ref where_filter) = op.where_filter {
1312 if !self.matches_where_filter(item_attrs, where_filter)? {
1313 return Ok(ModificationResult {
1315 changed: false,
1316 modified_nodes: vec![],
1317 });
1318 }
1319 }
1320
1321 let backup_node = BackupNode {
1323 node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1324 identifier: op.target_name.clone(),
1325 original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1326 location: self.span_to_location(item_span),
1327 };
1328
1329 let new_derives: Vec<String> = op.derives.iter()
1331 .filter(|d| !existing_derives.contains(&d.to_string()))
1332 .cloned()
1333 .collect();
1334
1335 if new_derives.is_empty() {
1336 return Ok(ModificationResult {
1338 changed: false,
1339 modified_nodes: vec![],
1340 });
1341 }
1342
1343 let mut all_derives = existing_derives;
1345 all_derives.extend(new_derives);
1346
1347 let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1349
1350 match &mut self.syntax_tree.items[item_index] {
1352 Item::Struct(s) => {
1353 Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1354 }
1355 Item::Enum(e) => {
1356 Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1357 }
1358 _ => unreachable!(),
1359 }
1360
1361 self.replace_formatted_item(item_index, item_span)?;
1363
1364 Ok(ModificationResult {
1365 changed: true,
1366 modified_nodes: vec![backup_node],
1367 })
1368 }
1369
1370 fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1372 let item_start_pos = self.span_to_byte_offset(original_span.start());
1374 let item_end_pos = self.span_to_byte_offset(original_span.end());
1375
1376 let mut actual_start = item_start_pos;
1378
1379 let mut temp_pos = item_start_pos;
1381 while temp_pos > 0 {
1382 temp_pos = temp_pos.saturating_sub(1);
1384 let mut line_start = temp_pos;
1385 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1386 line_start -= 1;
1387 }
1388
1389 let line = if temp_pos < self.content.len() {
1390 &self.content[line_start..temp_pos + 1]
1391 } else {
1392 &self.content[line_start..]
1393 };
1394 let trimmed = line.trim();
1395
1396 if trimmed.starts_with("#[") {
1397 actual_start = line_start;
1398 temp_pos = line_start;
1399 } else if trimmed.is_empty() {
1400 temp_pos = line_start;
1401 } else {
1402 break;
1403 }
1404
1405 if line_start == 0 {
1406 break;
1407 }
1408 }
1409
1410 let item_clone = self.syntax_tree.items[item_index].clone();
1412 let temp_file = syn::File {
1413 shebang: None,
1414 attrs: Vec::new(),
1415 items: vec![item_clone],
1416 };
1417
1418 let formatted = prettyplease::unparse(&temp_file);
1420 let formatted = formatted.trim();
1421
1422 self.content.replace_range(actual_start..item_end_pos, formatted);
1424
1425 Ok(())
1426 }
1427
1428 fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1430 for attr in attrs {
1431 if attr.path().is_ident("derive") {
1432 if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1433 let tokens_str = meta_list.tokens.to_string();
1434 return tokens_str
1435 .split(',')
1436 .map(|s| s.trim().to_string())
1437 .collect();
1438 }
1439 }
1440 }
1441 Vec::new()
1442 }
1443
1444 fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1449 if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1451 let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1452 let existing_derives = Self::extract_derives(attrs);
1453
1454 for required_trait in required_traits {
1456 if existing_derives.iter().any(|d| d == required_trait) {
1457 return Ok(true);
1458 }
1459 }
1460 return Ok(false);
1461 }
1462
1463 Ok(true)
1465 }
1466
1467 fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1469 let derive_str = derives.join(", ");
1470
1471 let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1473 let parsed: syn::ItemStruct = parse_str(&dummy)
1474 .context("Failed to parse derive attribute")?;
1475
1476 let new_attr = parsed.attrs.into_iter()
1477 .find(|a| a.path().is_ident("derive"))
1478 .context("Failed to extract derive attribute")?;
1479
1480 if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1482 attrs[pos] = new_attr;
1483 } else {
1484 attrs.insert(0, new_attr);
1486 }
1487
1488 Ok(())
1489 }
1490
1491 fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1493 if modified_function.is_none() {
1495 self.content = prettyplease::unparse(&self.syntax_tree);
1496 return Ok(());
1497 }
1498
1499 let original_syntax_tree: File = syn::parse_str(&self.content)
1501 .context("Failed to re-parse original content")?;
1502
1503 let function_name = modified_function.as_ref().unwrap();
1504
1505 let original_fn = original_syntax_tree.items.iter()
1507 .find_map(|item| {
1508 if let Item::Fn(f) = item {
1509 if f.sig.ident == function_name {
1510 return Some(f.clone());
1511 }
1512 }
1513 None
1514 })
1515 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1516
1517 let start = self.span_to_byte_offset(original_fn.span().start());
1519 let end = self.span_to_byte_offset(original_fn.span().end());
1520
1521 let modified_fn = self.syntax_tree.items.iter()
1523 .find_map(|item| {
1524 if let Item::Fn(f) = item {
1525 if f.sig.ident == function_name {
1526 return Some(f.clone());
1527 }
1528 }
1529 None
1530 })
1531 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1532
1533 let dummy_file = syn::File {
1535 shebang: None,
1536 attrs: Vec::new(),
1537 items: vec![Item::Fn(modified_fn)],
1538 };
1539
1540 let formatted_fn = prettyplease::unparse(&dummy_file);
1541
1542 let formatted_fn = formatted_fn.trim();
1544
1545 self.content.replace_range(start..end, formatted_fn);
1547
1548 Ok(())
1549 }
1550
1551 fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1552 let line_idx = pos.line.saturating_sub(1);
1553 if line_idx < self.line_offsets.len() {
1554 self.line_offsets[line_idx] + pos.column
1555 } else {
1556 self.content.len()
1557 }
1558 }
1559
1560 fn find_after_field_end(&self, pos: usize) -> usize {
1561 let mut i = pos;
1563 while i < self.content.len() {
1564 match self.content.as_bytes()[i] as char {
1565 ',' => return i + 1,
1566 '\n' => return i + 1,
1567 _ => i += 1,
1568 }
1569 }
1570 pos
1571 }
1572
1573 fn get_indentation(&self, pos: usize) -> String {
1574 let mut line_start = pos;
1576 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1577 line_start -= 1;
1578 }
1579
1580 let mut indent = String::new();
1582 let mut i = line_start;
1583 while i < self.content.len() {
1584 match self.content.as_bytes()[i] as char {
1585 ' ' | '\t' => {
1586 indent.push(self.content.as_bytes()[i] as char);
1587 i += 1;
1588 }
1589 _ => break,
1590 }
1591 }
1592
1593 if indent.is_empty() {
1595 " ".to_string()
1596 } else {
1597 indent
1598 }
1599 }
1600
1601 pub fn to_string(&self) -> String {
1602 self.content.clone()
1603 }
1604
1605 pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1607 use syn::visit::Visit;
1608 use crate::operations::InspectResult;
1609
1610 let mut results = Vec::new();
1611
1612 match node_type {
1613 "struct-literal" => {
1614 struct StructLiteralVisitor<'a> {
1616 results: &'a mut Vec<InspectResult>,
1617 name_filter: Option<&'a str>,
1618 editor: &'a RustEditor,
1619 }
1620
1621 impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1622 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1623 let struct_name = if let Some(ident) = node.path.get_ident() {
1625 ident.to_string()
1626 } else {
1627 node.path.segments.last()
1629 .map(|seg| seg.ident.to_string())
1630 .unwrap_or_default()
1631 };
1632
1633 if let Some(filter) = self.name_filter {
1635 if struct_name != filter {
1636 syn::visit::visit_expr_struct(self, node);
1637 return;
1638 }
1639 }
1640
1641 let snippet = self.editor.format_expr_struct(node);
1643 let location = self.editor.span_to_location(node.span());
1644
1645 self.results.push(InspectResult {
1646 file_path: String::new(), node_type: "ExprStruct".to_string(),
1648 identifier: struct_name,
1649 location,
1650 snippet,
1651 });
1652
1653 syn::visit::visit_expr_struct(self, node);
1655 }
1656 }
1657
1658 let mut visitor = StructLiteralVisitor {
1659 results: &mut results,
1660 name_filter,
1661 editor: self,
1662 };
1663
1664 for item in &self.syntax_tree.items {
1666 syn::visit::visit_item(&mut visitor, item);
1667 }
1668 }
1669 "match-arm" => {
1670 struct MatchArmVisitor<'a> {
1672 results: &'a mut Vec<InspectResult>,
1673 pattern_filter: Option<&'a str>,
1674 editor: &'a RustEditor,
1675 }
1676
1677 impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1678 fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1679 for arm in &node.arms {
1681 let pat = &arm.pat;
1683 let pattern_str = quote::quote!(#pat).to_string();
1684
1685 if let Some(filter) = self.pattern_filter {
1687 let normalized_pattern = pattern_str.replace(" ", "");
1689 let normalized_filter = filter.replace(" ", "");
1690
1691 if !normalized_pattern.contains(&normalized_filter) {
1692 continue;
1693 }
1694 }
1695
1696 let snippet = self.editor.format_match_arm(arm);
1698 let location = self.editor.span_to_location(arm.span());
1699
1700 self.results.push(InspectResult {
1701 file_path: String::new(), node_type: "MatchArm".to_string(),
1703 identifier: pattern_str.replace(" ", ""),
1704 location,
1705 snippet,
1706 });
1707 }
1708
1709 syn::visit::visit_expr_match(self, node);
1711 }
1712 }
1713
1714 let mut visitor = MatchArmVisitor {
1715 results: &mut results,
1716 pattern_filter: name_filter,
1717 editor: self,
1718 };
1719
1720 for item in &self.syntax_tree.items {
1722 syn::visit::visit_item(&mut visitor, item);
1723 }
1724 }
1725 "enum-usage" => {
1726 struct EnumUsageVisitor<'a> {
1728 results: &'a mut Vec<InspectResult>,
1729 path_filter: Option<&'a str>,
1730 editor: &'a RustEditor,
1731 }
1732
1733 impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1734 fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1735 let path = &node.path;
1737 let path_str = quote::quote!(#path).to_string();
1738
1739 if let Some(filter) = self.path_filter {
1741 let normalized_path = path_str.replace(" ", "");
1743 let normalized_filter = filter.replace(" ", "");
1744
1745 if !normalized_path.contains(&normalized_filter) {
1746 syn::visit::visit_expr_path(self, node);
1747 return;
1748 }
1749 }
1750
1751 let snippet = self.editor.format_expr_path(node);
1753 let location = self.editor.span_to_location(node.span());
1754
1755 self.results.push(InspectResult {
1756 file_path: String::new(), node_type: "ExprPath".to_string(),
1758 identifier: path_str.replace(" ", ""),
1759 location,
1760 snippet,
1761 });
1762
1763 syn::visit::visit_expr_path(self, node);
1765 }
1766 }
1767
1768 let mut visitor = EnumUsageVisitor {
1769 results: &mut results,
1770 path_filter: name_filter,
1771 editor: self,
1772 };
1773
1774 for item in &self.syntax_tree.items {
1776 syn::visit::visit_item(&mut visitor, item);
1777 }
1778 }
1779 "function-call" => {
1780 struct FunctionCallVisitor<'a> {
1782 results: &'a mut Vec<InspectResult>,
1783 name_filter: Option<&'a str>,
1784 editor: &'a RustEditor,
1785 }
1786
1787 impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
1788 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1789 let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1791 expr_path.path.segments.last()
1793 .map(|seg| seg.ident.to_string())
1794 .unwrap_or_default()
1795 } else {
1796 quote::quote!(#node.func).to_string()
1798 };
1799
1800 if let Some(filter) = self.name_filter {
1802 if func_name != filter {
1803 syn::visit::visit_expr_call(self, node);
1804 return;
1805 }
1806 }
1807
1808 let snippet = self.editor.format_expr_call(node);
1810 let location = self.editor.span_to_location(node.span());
1811
1812 self.results.push(InspectResult {
1813 file_path: String::new(), node_type: "ExprCall".to_string(),
1815 identifier: func_name,
1816 location,
1817 snippet,
1818 });
1819
1820 syn::visit::visit_expr_call(self, node);
1822 }
1823 }
1824
1825 let mut visitor = FunctionCallVisitor {
1826 results: &mut results,
1827 name_filter,
1828 editor: self,
1829 };
1830
1831 for item in &self.syntax_tree.items {
1833 syn::visit::visit_item(&mut visitor, item);
1834 }
1835 }
1836 "method-call" => {
1837 struct MethodCallVisitor<'a> {
1839 results: &'a mut Vec<InspectResult>,
1840 name_filter: Option<&'a str>,
1841 editor: &'a RustEditor,
1842 }
1843
1844 impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1845 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1846 let method_name = node.method.to_string();
1848
1849 if let Some(filter) = self.name_filter {
1851 if method_name != filter {
1852 syn::visit::visit_expr_method_call(self, node);
1853 return;
1854 }
1855 }
1856
1857 let snippet = self.editor.format_expr_method_call(node);
1859 let location = self.editor.span_to_location(node.span());
1860
1861 self.results.push(InspectResult {
1862 file_path: String::new(), node_type: "ExprMethodCall".to_string(),
1864 identifier: method_name,
1865 location,
1866 snippet,
1867 });
1868
1869 syn::visit::visit_expr_method_call(self, node);
1871 }
1872 }
1873
1874 let mut visitor = MethodCallVisitor {
1875 results: &mut results,
1876 name_filter,
1877 editor: self,
1878 };
1879
1880 for item in &self.syntax_tree.items {
1882 syn::visit::visit_item(&mut visitor, item);
1883 }
1884 }
1885 "identifier" => {
1886 struct IdentifierVisitor<'a> {
1888 results: &'a mut Vec<InspectResult>,
1889 name_filter: Option<&'a str>,
1890 editor: &'a RustEditor,
1891 }
1892
1893 impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
1894 fn visit_ident(&mut self, node: &'ast syn::Ident) {
1895 let ident_name = node.to_string();
1897
1898 if let Some(filter) = self.name_filter {
1900 if ident_name != filter {
1901 syn::visit::visit_ident(self, node);
1902 return;
1903 }
1904 }
1905
1906 let snippet = self.editor.format_ident(node);
1908 let location = self.editor.span_to_location(node.span());
1909
1910 self.results.push(InspectResult {
1911 file_path: String::new(), node_type: "Ident".to_string(),
1913 identifier: ident_name,
1914 location,
1915 snippet,
1916 });
1917
1918 syn::visit::visit_ident(self, node);
1920 }
1921 }
1922
1923 let mut visitor = IdentifierVisitor {
1924 results: &mut results,
1925 name_filter,
1926 editor: self,
1927 };
1928
1929 for item in &self.syntax_tree.items {
1931 syn::visit::visit_item(&mut visitor, item);
1932 }
1933 }
1934 "type-ref" => {
1935 struct TypeRefVisitor<'a> {
1937 results: &'a mut Vec<InspectResult>,
1938 name_filter: Option<&'a str>,
1939 editor: &'a RustEditor,
1940 }
1941
1942 impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
1943 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
1944 let type_name = node.path.segments.last()
1946 .map(|seg| seg.ident.to_string())
1947 .unwrap_or_default();
1948
1949 if let Some(filter) = self.name_filter {
1951 if type_name != filter {
1952 syn::visit::visit_type_path(self, node);
1953 return;
1954 }
1955 }
1956
1957 let snippet = self.editor.format_type_path(node);
1959 let location = self.editor.span_to_location(node.span());
1960
1961 let path = &node.path;
1963 let path_str = quote::quote!(#path).to_string();
1964
1965 self.results.push(InspectResult {
1966 file_path: String::new(), node_type: "TypePath".to_string(),
1968 identifier: path_str.replace(" ", ""),
1969 location,
1970 snippet,
1971 });
1972
1973 syn::visit::visit_type_path(self, node);
1975 }
1976 }
1977
1978 let mut visitor = TypeRefVisitor {
1979 results: &mut results,
1980 name_filter,
1981 editor: self,
1982 };
1983
1984 for item in &self.syntax_tree.items {
1986 syn::visit::visit_item(&mut visitor, item);
1987 }
1988 }
1989 _ => anyhow::bail!("Unsupported node type: {}", node_type),
1990 }
1991
1992 Ok(results)
1993 }
1994
1995 fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
1997 let start = self.span_to_byte_offset(expr.span().start());
1999 let end = self.span_to_byte_offset(expr.span().end());
2000
2001 let original = &self.content[start..end];
2003
2004 original.split_whitespace().collect::<Vec<_>>().join(" ")
2006 }
2007
2008 fn format_match_arm(&self, arm: &syn::Arm) -> String {
2010 let start = self.span_to_byte_offset(arm.span().start());
2012 let end = self.span_to_byte_offset(arm.span().end());
2013
2014 let original = &self.content[start..end];
2016
2017 original.split_whitespace().collect::<Vec<_>>().join(" ")
2019 }
2020
2021 fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2023 let start = self.span_to_byte_offset(expr.span().start());
2025 let end = self.span_to_byte_offset(expr.span().end());
2026
2027 let original = &self.content[start..end];
2029
2030 original.split_whitespace().collect::<Vec<_>>().join(" ")
2032 }
2033
2034 fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
2036 let start = self.span_to_byte_offset(expr.span().start());
2038 let end = self.span_to_byte_offset(expr.span().end());
2039
2040 let original = &self.content[start..end];
2042
2043 original.split_whitespace().collect::<Vec<_>>().join(" ")
2045 }
2046
2047 fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2049 let start = self.span_to_byte_offset(expr.span().start());
2051 let end = self.span_to_byte_offset(expr.span().end());
2052
2053 let original = &self.content[start..end];
2055
2056 original.split_whitespace().collect::<Vec<_>>().join(" ")
2058 }
2059
2060 fn format_ident(&self, ident: &syn::Ident) -> String {
2062 ident.to_string()
2063 }
2064
2065 fn format_type_path(&self, ty: &syn::TypePath) -> String {
2067 let start = self.span_to_byte_offset(ty.span().start());
2069 let end = self.span_to_byte_offset(ty.span().end());
2070
2071 let original = &self.content[start..end];
2073
2074 original.split_whitespace().collect::<Vec<_>>().join(" ")
2076 }
2077
2078 #[allow(dead_code)]
2080 pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2081 for (index, item) in self.syntax_tree.items.iter().enumerate() {
2082 match (node_type, item) {
2083 ("struct", Item::Struct(s)) if s.ident == name => {
2084 return Ok(index);
2085 }
2086 ("enum", Item::Enum(e)) if e.ident == name => {
2087 return Ok(index);
2088 }
2089 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2090 return Ok(index);
2091 }
2092 ("impl", Item::Impl(impl_block)) => {
2093 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2095 if let Some(segment) = type_path.path.segments.last() {
2096 if segment.ident == name {
2097 return Ok(index);
2098 }
2099 }
2100 }
2101 }
2102 _ => {}
2103 }
2104 }
2105
2106 anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2107 }
2108
2109 #[allow(dead_code)]
2111 pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2112 if index >= self.syntax_tree.items.len() {
2113 anyhow::bail!("Index {} out of bounds", index);
2114 }
2115
2116 self.syntax_tree.items[index] = new_item;
2118
2119 self.content = prettyplease::unparse(&self.syntax_tree);
2121
2122 self.line_offsets = Self::compute_line_offsets(&self.content);
2124
2125 Ok(())
2126 }
2127
2128 pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2129 let mut locations = Vec::new();
2130
2131 for item in &self.syntax_tree.items {
2132 match (node_type, item) {
2133 ("struct", Item::Struct(s)) if s.ident == name => {
2134 locations.push(self.span_to_location(s.span()));
2135 }
2136 ("enum", Item::Enum(e)) if e.ident == name => {
2137 locations.push(self.span_to_location(e.span()));
2138 }
2139 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2140 locations.push(self.span_to_location(f.span()));
2141 }
2142 _ => {}
2143 }
2144 }
2145
2146 if locations.is_empty() {
2147 anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2148 }
2149
2150 Ok(locations)
2151 }
2152
2153 fn span_to_location(&self, span: Span) -> NodeLocation {
2154 let start = span.start();
2155 let end = span.end();
2156
2157 NodeLocation {
2158 line: start.line,
2159 column: start.column,
2160 end_line: end.line,
2161 end_column: end.column,
2162 }
2163 }
2164}
2165
2166struct MatchArmAdder {
2168 target_function: Option<String>,
2169 arm_to_add: Arm,
2170 modified: bool,
2171 current_function: Option<String>,
2172 modified_function: Option<String>,
2173}
2174
2175impl VisitMut for MatchArmAdder {
2176 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2177 let prev_fn = self.current_function.clone();
2178 self.current_function = Some(node.sig.ident.to_string());
2179
2180 syn::visit_mut::visit_item_fn_mut(self, node);
2182
2183 self.current_function = prev_fn;
2184 }
2185
2186 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2187 if let Some(ref target) = self.target_function {
2189 if self.current_function.as_ref() != Some(target) {
2190 syn::visit_mut::visit_expr_match_mut(self, node);
2192 return;
2193 }
2194 }
2195
2196 let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2198 let already_exists = node.arms.iter().any(|arm| {
2199 arm.pat.to_token_stream().to_string() == pattern_str
2200 });
2201
2202 if !already_exists {
2203 node.arms.push(self.arm_to_add.clone());
2205 self.modified = true;
2206 self.modified_function = self.current_function.clone();
2207 }
2208
2209 syn::visit_mut::visit_expr_match_mut(self, node);
2211 }
2212}
2213
2214struct MatchArmUpdater {
2216 target_function: Option<String>,
2217 pattern_to_match: String,
2218 new_body: syn::Expr,
2219 modified: bool,
2220 current_function: Option<String>,
2221 modified_function: Option<String>,
2222}
2223
2224impl VisitMut for MatchArmUpdater {
2225 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2226 let prev_fn = self.current_function.clone();
2227 self.current_function = Some(node.sig.ident.to_string());
2228
2229 syn::visit_mut::visit_item_fn_mut(self, node);
2230
2231 self.current_function = prev_fn;
2232 }
2233
2234 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2235 if let Some(ref target) = self.target_function {
2237 if self.current_function.as_ref() != Some(target) {
2238 syn::visit_mut::visit_expr_match_mut(self, node);
2239 return;
2240 }
2241 }
2242
2243 for arm in &mut node.arms {
2245 let pattern_str = arm.pat.to_token_stream().to_string();
2246 let pattern_normalized = pattern_str.replace(" ", "");
2248 let target_normalized = self.pattern_to_match.replace(" ", "");
2249
2250 if pattern_normalized == target_normalized {
2251 arm.body = Box::new(self.new_body.clone());
2252 self.modified = true;
2253 self.modified_function = self.current_function.clone();
2254 break;
2255 }
2256 }
2257
2258 syn::visit_mut::visit_expr_match_mut(self, node);
2259 }
2260}
2261
2262struct MatchArmRemover {
2264 target_function: Option<String>,
2265 pattern_to_remove: String,
2266 modified: bool,
2267 current_function: Option<String>,
2268 modified_function: Option<String>,
2269}
2270
2271impl VisitMut for MatchArmRemover {
2272 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2273 let prev_fn = self.current_function.clone();
2274 self.current_function = Some(node.sig.ident.to_string());
2275
2276 syn::visit_mut::visit_item_fn_mut(self, node);
2277
2278 self.current_function = prev_fn;
2279 }
2280
2281 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2282 if let Some(ref target) = self.target_function {
2284 if self.current_function.as_ref() != Some(target) {
2285 syn::visit_mut::visit_expr_match_mut(self, node);
2286 return;
2287 }
2288 }
2289
2290 let mut index_to_remove = None;
2292 for (i, arm) in node.arms.iter().enumerate() {
2293 let pattern_str = arm.pat.to_token_stream().to_string();
2294 let pattern_normalized = pattern_str.replace(" ", "");
2296 let target_normalized = self.pattern_to_remove.replace(" ", "");
2297
2298 if pattern_normalized == target_normalized {
2299 index_to_remove = Some(i);
2300 break;
2301 }
2302 }
2303
2304 if let Some(index) = index_to_remove {
2305 node.arms.remove(index);
2306 self.modified = true;
2307 self.modified_function = self.current_function.clone();
2308 }
2309
2310 syn::visit_mut::visit_expr_match_mut(self, node);
2311 }
2312}
2313
2314struct MultiMatchArmAdder {
2316 target_function: Option<String>,
2317 arms_to_add: Vec<(String, Arm)>, modified: bool,
2319 current_function: Option<String>,
2320 modified_function: Option<String>,
2321}
2322
2323impl VisitMut for MultiMatchArmAdder {
2324 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2325 let prev_fn = self.current_function.clone();
2326 self.current_function = Some(node.sig.ident.to_string());
2327
2328 syn::visit_mut::visit_item_fn_mut(self, node);
2329
2330 self.current_function = prev_fn;
2331 }
2332
2333 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2334 if let Some(ref target) = self.target_function {
2336 if self.current_function.as_ref() != Some(target) {
2337 syn::visit_mut::visit_expr_match_mut(self, node);
2338 return;
2339 }
2340 }
2341
2342 for (pattern_str, arm) in &self.arms_to_add {
2344 let already_exists = node.arms.iter().any(|existing_arm| {
2346 existing_arm.pat.to_token_stream().to_string() == *pattern_str
2347 });
2348
2349 if !already_exists {
2350 node.arms.push(arm.clone());
2351 self.modified = true;
2352 self.modified_function = self.current_function.clone();
2353 }
2354 }
2355
2356 syn::visit_mut::visit_expr_match_mut(self, node);
2357 }
2358}
2359
2360struct StructLiteralFieldAdder {
2362 struct_name: String,
2363 field_def: String,
2364 field_name: String,
2365 position: InsertPosition,
2366 modified: bool,
2367}
2368
2369impl VisitMut for StructLiteralFieldAdder {
2370 fn visit_expr_mut(&mut self, node: &mut Expr) {
2371 if let Expr::Struct(expr_struct) = node {
2373 let struct_name = expr_struct.path.segments.last()
2375 .map(|seg| seg.ident.to_string());
2376
2377 if struct_name.as_ref() == Some(&self.struct_name) {
2378 let field_exists = expr_struct.fields.iter().any(|fv| {
2380 fv.member.to_token_stream().to_string() == self.field_name
2381 });
2382
2383 if !field_exists {
2384 let field_value_code = format!("{{ {} }}", self.field_def);
2387 if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
2388 if let Some(new_fv) = expr.fields.first() {
2389 match &self.position {
2391 InsertPosition::First => {
2392 expr_struct.fields.insert(0, new_fv.clone());
2393 self.modified = true;
2394 }
2395 InsertPosition::Last => {
2396 expr_struct.fields.push(new_fv.clone());
2397 self.modified = true;
2398 }
2399 InsertPosition::After(after_field) => {
2400 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2402 fv.member.to_token_stream().to_string() == *after_field
2403 }) {
2404 expr_struct.fields.insert(pos + 1, new_fv.clone());
2405 self.modified = true;
2406 }
2407 }
2408 InsertPosition::Before(before_field) => {
2409 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2411 fv.member.to_token_stream().to_string() == *before_field
2412 }) {
2413 expr_struct.fields.insert(pos, new_fv.clone());
2414 self.modified = true;
2415 }
2416 }
2417 }
2418 }
2419 }
2420 }
2421 }
2422 }
2423
2424 syn::visit_mut::visit_expr_mut(self, node);
2427 }
2428}