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 crate::path_resolver::PathResolver;
12use prettyplease;
13
14pub struct RustEditor {
15 content: String,
16 syntax_tree: File,
17 line_offsets: Vec<usize>, }
19
20impl RustEditor {
21 pub fn new(content: &str) -> Result<Self> {
22 let syntax_tree: File = syn::parse_str(content)
23 .context("Failed to parse Rust code")?;
24
25 let line_offsets = Self::compute_line_offsets(content);
26
27 Ok(Self {
28 content: content.to_string(),
29 syntax_tree,
30 line_offsets,
31 })
32 }
33
34 fn format_field(field: &Field) -> String {
36 let mut result = String::new();
37
38 if let syn::Visibility::Public(_) = field.vis {
40 result.push_str("pub ");
41 }
42
43 if let Some(ident) = &field.ident {
45 result.push_str(&ident.to_string());
46 }
47
48 result.push_str(": ");
50
51 let type_str = field.ty.to_token_stream().to_string();
53 let type_str = type_str.replace(" < ", "<").replace(" >", ">");
54 result.push_str(&type_str);
55
56 result
57 }
58
59 fn compute_line_offsets(content: &str) -> Vec<usize> {
60 let mut offsets = vec![0];
61 for (i, ch) in content.char_indices() {
62 if ch == '\n' {
63 offsets.push(i + 1);
64 }
65 }
66 offsets
67 }
68
69 pub fn apply_operation(&mut self, op: &Operation) -> Result<ModificationResult> {
70 match op {
71 Operation::AddStructField(op) => self.add_struct_field(op),
72 Operation::UpdateStructField(op) => self.update_struct_field(op),
73 Operation::RemoveStructField(op) => self.remove_struct_field(op),
74 Operation::AddStructLiteralField(op) => self.add_struct_literal_field(op),
75 Operation::AddEnumVariant(op) => self.add_enum_variant(op),
76 Operation::UpdateEnumVariant(op) => self.update_enum_variant(op),
77 Operation::RemoveEnumVariant(op) => self.remove_enum_variant(op),
78 Operation::AddMatchArm(op) => self.add_match_arm(op),
79 Operation::UpdateMatchArm(op) => self.update_match_arm(op),
80 Operation::RemoveMatchArm(op) => self.remove_match_arm(op),
81 Operation::AddImplMethod(op) => self.add_impl_method(op),
82 Operation::AddUseStatement(op) => self.add_use_statement(op),
83 Operation::AddDerive(op) => self.add_derive(op),
84 Operation::Transform(op) => self.transform(op),
85 Operation::RenameEnumVariant(op) => self.rename_enum_variant(op),
86 Operation::RenameFunction(op) => self.rename_function(op),
87 Operation::AddDocComment(op) => self.add_doc_comment_surgical(
88 &op.target_type,
89 &op.name,
90 &op.doc_comment,
91 &op.style,
92 ),
93 Operation::UpdateDocComment(op) => self.update_doc_comment_surgical(
94 &op.target_type,
95 &op.name,
96 &op.doc_comment,
97 &DocCommentStyle::Line, ),
99 Operation::RemoveDocComment(op) => self.remove_doc_comment_surgical(
100 &op.target_type,
101 &op.name,
102 ),
103 }
104 }
105
106 pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
107 let mut modified_nodes = Vec::new();
108
109 let item_struct = self.syntax_tree.items.iter()
111 .find_map(|item| {
112 if let Item::Struct(s) = item {
113 if s.ident == op.struct_name {
114 return Some(s.clone());
115 }
116 }
117 None
118 })
119 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
120
121 if let Some(ref where_filter) = op.where_filter {
123 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
124 return Ok(ModificationResult {
126 changed: false,
127 modified_nodes: vec![],
128 });
129 }
130 }
131
132 if op.literal_default.is_none() {
134 let backup_node = BackupNode {
136 node_type: "ItemStruct".to_string(),
137 identifier: op.struct_name.clone(),
138 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
139 location: self.span_to_location(item_struct.span()),
140 };
141
142 let modified = self.insert_struct_field(&item_struct, op)
144 .context("Failed to add field to struct definition")?;
145
146 if !modified {
147 return Ok(ModificationResult {
148 changed: false,
149 modified_nodes: vec![],
150 });
151 }
152
153 return Ok(ModificationResult {
154 changed: true,
155 modified_nodes: vec![backup_node],
156 });
157 }
158
159 let literal_default = op.literal_default.as_ref().unwrap();
163
164 let has_type = op.field_def.contains(':');
167
168 let mut def_modified = false;
169 if has_type {
170 let backup_node = BackupNode {
172 node_type: "ItemStruct".to_string(),
173 identifier: op.struct_name.clone(),
174 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
175 location: self.span_to_location(item_struct.span()),
176 };
177
178 def_modified = self.insert_struct_field(&item_struct, op)
180 .context("Failed to add field to struct definition")?;
181
182 if def_modified {
183 modified_nodes.push(backup_node);
184 self.syntax_tree = syn::parse_str(&self.content)
186 .context("Failed to re-parse content after adding struct field")?;
187 self.line_offsets = Self::compute_line_offsets(&self.content);
188 }
189 }
190
191 let field_name = op.field_def.split(':')
194 .next()
195 .map(|s| s.trim().to_string())
196 .context("Failed to extract field name from field definition")?;
197
198 let literal_op = AddStructLiteralFieldOp {
200 struct_name: op.struct_name.clone(),
201 field_def: format!("{}: {}", field_name, literal_default),
202 position: op.position.clone(),
203 struct_path: None, };
205
206 let literal_result = self.add_struct_literal_field(&literal_op)
208 .context("Failed to update struct literals")?;
209 modified_nodes.extend(literal_result.modified_nodes);
210
211 Ok(ModificationResult {
212 changed: true,
213 modified_nodes,
214 })
215 }
216
217 fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
218 if let Fields::Named(ref fields) = item_struct.fields {
219 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
221 let dummy: ItemStruct = parse_str(&field_code)
222 .context("Failed to parse field definition")?;
223
224 let new_field = if let Fields::Named(ref nf) = dummy.fields {
225 nf.named.first()
226 .context("No field found in definition")?
227 .clone()
228 } else {
229 anyhow::bail!("Expected named field");
230 };
231
232 let new_field_name = new_field.ident.as_ref()
234 .map(|i| i.to_string())
235 .context("Field must have a name")?;
236
237 if fields.named.iter().any(|f| {
238 f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
239 }) {
240 return Ok(false);
242 }
243
244 let insert_pos = match &op.position {
246 InsertPosition::First => {
247 if let Some(first_field) = fields.named.first() {
248 self.span_to_byte_offset(first_field.span().start())
249 } else {
250 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
252 brace_pos + 1
253 }
254 }
255 InsertPosition::Last => {
256 if let Some(last_field) = fields.named.last() {
257 let end = self.span_to_byte_offset(last_field.span().end());
258 self.find_after_field_end(end)
260 } else {
261 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
263 brace_pos + 1
264 }
265 }
266 InsertPosition::After(name) => {
267 let field = fields.named.iter()
268 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
269 .with_context(|| format!("Field '{}' not found", name))?;
270 let end = self.span_to_byte_offset(field.span().end());
271 self.find_after_field_end(end)
272 }
273 InsertPosition::Before(name) => {
274 let field = fields.named.iter()
275 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
276 .with_context(|| format!("Field '{}' not found", name))?;
277 self.span_to_byte_offset(field.span().start())
278 }
279 };
280
281 let indent = self.get_indentation(insert_pos);
283 let field_str = Self::format_field(&new_field);
284 let insert_text = if matches!(op.position, InsertPosition::First) {
285 format!("\n{}{},", indent, field_str)
286 } else {
287 format!("\n{}{},", indent, field_str)
288 };
289
290 self.content.insert_str(insert_pos, &insert_text);
291 return Ok(true);
292 }
293
294 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
295 }
296
297 pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
298 let item_struct = self.syntax_tree.items.iter()
300 .find_map(|item| {
301 if let Item::Struct(s) = item {
302 if s.ident == op.struct_name {
303 return Some(s.clone());
304 }
305 }
306 None
307 })
308 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
309
310 if let Some(ref where_filter) = op.where_filter {
312 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
313 return Ok(ModificationResult {
315 changed: false,
316 modified_nodes: vec![],
317 });
318 }
319 }
320
321 let backup_node = BackupNode {
323 node_type: "ItemStruct".to_string(),
324 identifier: op.struct_name.clone(),
325 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
326 location: self.span_to_location(item_struct.span()),
327 };
328
329 let modified = self.replace_struct_field(&item_struct, op)?;
330
331 Ok(ModificationResult {
332 changed: modified,
333 modified_nodes: if modified { vec![backup_node] } else { vec![] },
334 })
335 }
336
337 fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
338 if let Fields::Named(ref fields) = item_struct.fields {
339 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
341 let dummy: ItemStruct = parse_str(&field_code)
342 .context("Failed to parse field definition")?;
343
344 let new_field = if let Fields::Named(ref nf) = dummy.fields {
345 nf.named.first()
346 .context("No field found in definition")?
347 .clone()
348 } else {
349 anyhow::bail!("Expected named field");
350 };
351
352 let field_name = new_field.ident.as_ref()
354 .map(|i| i.to_string())
355 .context("Field must have a name")?;
356
357 let existing_field = fields.named.iter()
359 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
360 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
361
362 let start = self.span_to_byte_offset(existing_field.span().start());
364 let end = self.span_to_byte_offset(existing_field.span().end());
365
366 let new_field_str = Self::format_field(&new_field);
368
369 self.content.replace_range(start..end, &new_field_str);
371
372 return Ok(true);
373 }
374
375 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
376 }
377
378 pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
379 let item_struct = self.syntax_tree.items.iter()
381 .find_map(|item| {
382 if let Item::Struct(s) = item {
383 if s.ident == op.struct_name {
384 return Some(s.clone());
385 }
386 }
387 None
388 })
389 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
390
391 if let Some(ref where_filter) = op.where_filter {
393 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
394 return Ok(ModificationResult {
396 changed: false,
397 modified_nodes: vec![],
398 });
399 }
400 }
401
402 let backup_node = BackupNode {
404 node_type: "ItemStruct".to_string(),
405 identifier: op.struct_name.clone(),
406 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
407 location: self.span_to_location(item_struct.span()),
408 };
409
410 if let Fields::Named(ref fields) = item_struct.fields {
411 let field_to_remove = fields.named.iter()
413 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
414 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
415
416 let start = self.span_to_byte_offset(field_to_remove.span().start());
418 let mut end = self.span_to_byte_offset(field_to_remove.span().end());
419
420 while end < self.content.len() {
422 match self.content.as_bytes()[end] as char {
423 ',' => {
424 end += 1;
425 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
427 end += 1;
428 }
429 break;
430 }
431 ' ' | '\t' => end += 1,
432 '\n' => {
433 end += 1;
434 break;
435 }
436 _ => break,
437 }
438 }
439
440 let mut line_start = start;
442 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
443 line_start -= 1;
444 }
445
446 let before_field = &self.content[line_start..start];
448 if before_field.trim().is_empty() {
449 self.content.replace_range(line_start..end, "");
451 } else {
452 self.content.replace_range(start..end, "");
454 }
455
456 return Ok(ModificationResult {
457 changed: true,
458 modified_nodes: vec![backup_node],
459 });
460 }
461
462 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
463 }
464
465 pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
466 let field_name = op.field_def.split(':')
468 .next()
469 .map(|s| s.trim().to_string())
470 .context("Field definition must contain ':'")?;
471
472 let path_resolver = if let Some(struct_path) = &op.struct_path {
474 let mut resolver = PathResolver::new(struct_path)
475 .ok_or_else(|| anyhow::anyhow!("Invalid struct path: {}", struct_path))?;
476
477 resolver.scan_file(&self.syntax_tree);
479 Some(resolver)
480 } else {
481 None
482 };
483
484 let backup_nodes = self.collect_struct_literal_backups(&op.struct_name, path_resolver.as_ref());
486
487 let mut visitor = StructLiteralFieldAdder {
489 struct_name: op.struct_name.clone(),
490 field_def: op.field_def.clone(),
491 field_name,
492 position: op.position.clone(),
493 path_resolver,
494 modified: false,
495 };
496
497 visitor.visit_file_mut(&mut self.syntax_tree);
498
499 if visitor.modified {
500 self.content = prettyplease::unparse(&self.syntax_tree);
502 Ok(ModificationResult {
503 changed: true,
504 modified_nodes: backup_nodes,
505 })
506 } else {
507 Ok(ModificationResult {
508 changed: false,
509 modified_nodes: vec![],
510 })
511 }
512 }
513
514 fn collect_struct_literal_backups(&self, struct_name: &str, path_resolver: Option<&PathResolver>) -> Vec<BackupNode> {
516 use syn::visit::Visit;
517
518 struct LiteralCollector<'a> {
519 struct_name: String,
520 path_resolver: Option<&'a PathResolver>,
521 backups: Vec<BackupNode>,
522 counter: usize,
523 }
524
525 impl<'ast, 'a> Visit<'ast> for LiteralCollector<'a> {
526 fn visit_expr(&mut self, node: &'ast Expr) {
527 if let Expr::Struct(expr_struct) = node {
528 let matches = if let Some(resolver) = self.path_resolver {
529 resolver.matches_target(&expr_struct.path)
531 } else {
532 if self.struct_name.contains("::") {
538 if self.struct_name.starts_with("*::") {
540 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
543 .map(|seg| seg.ident.to_string() == target_name)
544 .unwrap_or(false)
545 } else {
546 let path_str = expr_struct.path.segments.iter()
548 .map(|seg| seg.ident.to_string())
549 .collect::<Vec<_>>()
550 .join("::");
551 path_str == self.struct_name
552 }
553 } else {
554 expr_struct.path.segments.len() == 1
556 && expr_struct.path.segments.last()
557 .map(|seg| seg.ident.to_string() == self.struct_name)
558 .unwrap_or(false)
559 }
560 };
561
562 if matches {
563 self.backups.push(BackupNode {
564 node_type: "ExprStruct".to_string(),
565 identifier: format!("{}#{}", self.struct_name, self.counter),
566 original_content: expr_struct.to_token_stream().to_string(),
567 location: NodeLocation {
568 line: 0, column: 0,
570 end_line: 0,
571 end_column: 0,
572 },
573 });
574 self.counter += 1;
575 }
576 }
577 syn::visit::visit_expr(self, node);
578 }
579 }
580
581 let mut collector = LiteralCollector {
582 struct_name: struct_name.to_string(),
583 path_resolver,
584 backups: Vec::new(),
585 counter: 0,
586 };
587
588 collector.visit_file(&self.syntax_tree);
589 collector.backups
590 }
591
592 pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
593 let item_enum = self.syntax_tree.items.iter()
595 .find_map(|item| {
596 if let Item::Enum(e) = item {
597 if e.ident == op.enum_name {
598 return Some(e.clone());
599 }
600 }
601 None
602 })
603 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
604
605 if let Some(ref where_filter) = op.where_filter {
607 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
608 return Ok(ModificationResult {
610 changed: false,
611 modified_nodes: vec![],
612 });
613 }
614 }
615
616 let backup_node = BackupNode {
618 node_type: "ItemEnum".to_string(),
619 identifier: op.enum_name.clone(),
620 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
621 location: self.span_to_location(item_enum.span()),
622 };
623
624 let modified = self.insert_enum_variant(&item_enum, op)?;
625
626 Ok(ModificationResult {
627 changed: modified,
628 modified_nodes: if modified { vec![backup_node] } else { vec![] },
629 })
630 }
631
632 fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
633 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
635 let dummy: ItemEnum = parse_str(&variant_code)
636 .context("Failed to parse variant definition")?;
637
638 let new_variant = dummy.variants.first()
639 .context("No variant found in definition")?
640 .clone();
641
642 let variant_name = new_variant.ident.to_string();
644 if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
645 return Ok(false);
647 }
648
649 let insert_pos = match &op.position {
651 InsertPosition::First => {
652 if let Some(first_var) = item_enum.variants.first() {
653 self.span_to_byte_offset(first_var.span().start())
654 } else {
655 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
656 brace_pos + 1
657 }
658 }
659 InsertPosition::Last => {
660 if let Some(last_var) = item_enum.variants.last() {
661 let end = self.span_to_byte_offset(last_var.span().end());
662 self.find_after_field_end(end)
663 } else {
664 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
665 brace_pos + 1
666 }
667 }
668 InsertPosition::After(name) => {
669 let variant = item_enum.variants.iter()
670 .find(|v| v.ident.to_string() == *name)
671 .with_context(|| format!("Variant '{}' not found", name))?;
672 let end = self.span_to_byte_offset(variant.span().end());
673 self.find_after_field_end(end)
674 }
675 InsertPosition::Before(name) => {
676 let variant = item_enum.variants.iter()
677 .find(|v| v.ident.to_string() == *name)
678 .with_context(|| format!("Variant '{}' not found", name))?;
679 self.span_to_byte_offset(variant.span().start())
680 }
681 };
682
683 let indent = self.get_indentation(insert_pos);
684 let variant_str = new_variant.to_token_stream().to_string();
685 let insert_text = format!("\n{}{},", indent, variant_str);
686
687 self.content.insert_str(insert_pos, &insert_text);
688 Ok(true)
689 }
690
691 fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
692 let item_enum = self.syntax_tree.items.iter()
694 .find_map(|item| {
695 if let Item::Enum(e) = item {
696 if e.ident == op.enum_name {
697 return Some(e.clone());
698 }
699 }
700 None
701 })
702 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
703
704 if let Some(ref where_filter) = op.where_filter {
706 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
707 return Ok(ModificationResult {
709 changed: false,
710 modified_nodes: vec![],
711 });
712 }
713 }
714
715 let backup_node = BackupNode {
717 node_type: "ItemEnum".to_string(),
718 identifier: op.enum_name.clone(),
719 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
720 location: self.span_to_location(item_enum.span()),
721 };
722
723 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
725 let dummy: ItemEnum = parse_str(&variant_code)
726 .context("Failed to parse variant definition")?;
727
728 let new_variant = dummy.variants.first()
729 .context("No variant found in definition")?
730 .clone();
731
732 let variant_name = new_variant.ident.to_string();
733
734 let existing_variant = item_enum.variants.iter()
736 .find(|v| v.ident.to_string() == variant_name)
737 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
738
739 let start = self.span_to_byte_offset(existing_variant.span().start());
741 let end = self.span_to_byte_offset(existing_variant.span().end());
742
743 let variant_str = new_variant.to_token_stream().to_string();
745 self.content.replace_range(start..end, &variant_str);
746
747 Ok(ModificationResult {
748 changed: true,
749 modified_nodes: vec![backup_node],
750 })
751 }
752
753 pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
754 let item_enum = self.syntax_tree.items.iter()
756 .find_map(|item| {
757 if let Item::Enum(e) = item {
758 if e.ident == op.enum_name {
759 return Some(e.clone());
760 }
761 }
762 None
763 })
764 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
765
766 if let Some(ref where_filter) = op.where_filter {
768 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
769 return Ok(ModificationResult {
771 changed: false,
772 modified_nodes: vec![],
773 });
774 }
775 }
776
777 let backup_node = BackupNode {
779 node_type: "ItemEnum".to_string(),
780 identifier: op.enum_name.clone(),
781 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
782 location: self.span_to_location(item_enum.span()),
783 };
784
785 let variant_to_remove = item_enum.variants.iter()
787 .find(|v| v.ident.to_string() == op.variant_name)
788 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
789
790 let start = self.span_to_byte_offset(variant_to_remove.span().start());
792 let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
793
794 while end < self.content.len() {
796 match self.content.as_bytes()[end] as char {
797 ',' => {
798 end += 1;
799 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
800 end += 1;
801 }
802 break;
803 }
804 ' ' | '\t' => end += 1,
805 '\n' => {
806 end += 1;
807 break;
808 }
809 _ => break,
810 }
811 }
812
813 let mut line_start = start;
815 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
816 line_start -= 1;
817 }
818
819 let before_variant = &self.content[line_start..start];
820 if before_variant.trim().is_empty() {
821 self.content.replace_range(line_start..end, "");
822 } else {
823 self.content.replace_range(start..end, "");
824 }
825
826 Ok(ModificationResult {
827 changed: true,
828 modified_nodes: vec![backup_node],
829 })
830 }
831
832 pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
833 if op.auto_detect {
834 self.add_missing_match_arms(op)
836 } else {
837 self.add_single_match_arm(op)
839 }
840 }
841
842 fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
843 let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
845 let expr: syn::Expr = parse_str(&dummy_match)
846 .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
847
848 let arm = if let syn::Expr::Match(match_expr) = expr {
850 match_expr.arms.into_iter().next()
851 .context("Failed to extract arm from dummy match")?
852 } else {
853 anyhow::bail!("Expected match expression");
854 };
855
856 let backup_node = if let Some(ref fn_name) = op.function_name {
858 self.get_function_backup(fn_name)?
859 } else {
860 BackupNode {
863 node_type: "Unknown".to_string(),
864 identifier: "match_expression".to_string(),
865 original_content: String::new(),
866 location: NodeLocation {
867 line: 0,
868 column: 0,
869 end_line: 0,
870 end_column: 0,
871 },
872 }
873 };
874
875 let mut visitor = MatchArmAdder {
877 target_function: op.function_name.clone(),
878 arm_to_add: arm,
879 modified: false,
880 current_function: None,
881 modified_function: None,
882 };
883
884 visitor.visit_file_mut(&mut self.syntax_tree);
885
886 if visitor.modified {
887 self.replace_modified_functions(&visitor.modified_function)?;
889 Ok(ModificationResult {
890 changed: true,
891 modified_nodes: vec![backup_node],
892 })
893 } else {
894 Ok(ModificationResult {
895 changed: false,
896 modified_nodes: vec![],
897 })
898 }
899 }
900
901 fn unparse_item(&self, item: &Item) -> String {
903 let temp_file = syn::File {
904 shebang: None,
905 attrs: Vec::new(),
906 items: vec![item.clone()],
907 };
908 prettyplease::unparse(&temp_file).trim().to_string()
909 }
910
911 fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
913 for item in &self.syntax_tree.items {
914 if let Item::Fn(f) = item {
915 if f.sig.ident == fn_name {
916 return Ok(BackupNode {
917 node_type: "ItemFn".to_string(),
918 identifier: fn_name.to_string(),
919 original_content: self.unparse_item(&Item::Fn(f.clone())),
920 location: self.span_to_location(f.span()),
921 });
922 }
923 }
924 }
925 anyhow::bail!("Function '{}' not found", fn_name)
926 }
927
928 fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
929 let enum_name = op.enum_name.as_ref()
931 .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
932
933 let enum_variants = self.find_enum_variants(enum_name)?;
935
936 if enum_variants.is_empty() {
937 anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
938 }
939
940 let existing_patterns = self.find_existing_match_patterns(&op.function_name);
942
943 let mut missing_variants = Vec::new();
945 for variant in &enum_variants {
946 let pattern = format!("{}::{}", enum_name, variant);
947 let pattern_normalized = pattern.replace(" ", "");
948
949 let exists = existing_patterns.iter().any(|p| {
950 p.replace(" ", "") == pattern_normalized
951 });
952
953 if !exists {
954 missing_variants.push(variant.clone());
955 }
956 }
957
958 if missing_variants.is_empty() {
959 println!("All enum variants already covered in match expressions");
960 return Ok(ModificationResult {
961 changed: false,
962 modified_nodes: vec![],
963 });
964 }
965
966 let backup_node = if let Some(ref fn_name) = op.function_name {
968 self.get_function_backup(fn_name)?
969 } else {
970 BackupNode {
971 node_type: "Unknown".to_string(),
972 identifier: "match_expression".to_string(),
973 original_content: String::new(),
974 location: NodeLocation {
975 line: 0,
976 column: 0,
977 end_line: 0,
978 end_column: 0,
979 },
980 }
981 };
982
983 let mut arms_to_add = Vec::new();
985 for variant in &missing_variants {
986 let pattern = format!("{}::{}", enum_name, variant);
987 let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
988 let expr: syn::Expr = parse_str(&dummy_match)
989 .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
990
991 if let syn::Expr::Match(match_expr) = expr {
992 if let Some(arm) = match_expr.arms.into_iter().next() {
993 arms_to_add.push((pattern.clone(), arm));
994 }
995 }
996 }
997
998 let mut visitor = MultiMatchArmAdder {
1000 target_function: op.function_name.clone(),
1001 arms_to_add,
1002 modified: false,
1003 current_function: None,
1004 modified_function: None,
1005 };
1006
1007 visitor.visit_file_mut(&mut self.syntax_tree);
1008
1009 if visitor.modified {
1010 for variant in &missing_variants {
1012 println!("Added match arm for: {}::{}", enum_name, variant);
1013 }
1014
1015 self.replace_modified_functions(&visitor.modified_function)?;
1017 Ok(ModificationResult {
1018 changed: true,
1019 modified_nodes: vec![backup_node],
1020 })
1021 } else {
1022 Ok(ModificationResult {
1023 changed: false,
1024 modified_nodes: vec![],
1025 })
1026 }
1027 }
1028
1029 fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
1030 for item in &self.syntax_tree.items {
1032 if let Item::Enum(e) = item {
1033 if e.ident == enum_name {
1034 let variants: Vec<String> = e.variants.iter()
1035 .map(|v| v.ident.to_string())
1036 .collect();
1037 return Ok(variants);
1038 }
1039 }
1040 }
1041
1042 Ok(Vec::new())
1043 }
1044
1045 fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1046 use syn::visit::Visit;
1047
1048 struct PatternCollector {
1049 target_function: Option<String>,
1050 current_function: Option<String>,
1051 patterns: Vec<String>,
1052 }
1053
1054 impl<'ast> Visit<'ast> for PatternCollector {
1055 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1056 let prev_fn = self.current_function.clone();
1057 self.current_function = Some(node.sig.ident.to_string());
1058 syn::visit::visit_item_fn(self, node);
1059 self.current_function = prev_fn;
1060 }
1061
1062 fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1063 if let Some(ref target) = self.target_function {
1065 if self.current_function.as_ref() != Some(target) {
1066 syn::visit::visit_expr_match(self, node);
1067 return;
1068 }
1069 }
1070
1071 for arm in &node.arms {
1073 self.patterns.push(arm.pat.to_token_stream().to_string());
1074 }
1075
1076 syn::visit::visit_expr_match(self, node);
1077 }
1078 }
1079
1080 let mut collector = PatternCollector {
1081 target_function: function_name.clone(),
1082 current_function: None,
1083 patterns: Vec::new(),
1084 };
1085
1086 collector.visit_file(&self.syntax_tree);
1087 collector.patterns
1088 }
1089
1090 pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1091 let backup_node = if let Some(ref fn_name) = op.function_name {
1093 self.get_function_backup(fn_name)?
1094 } else {
1095 BackupNode {
1096 node_type: "Unknown".to_string(),
1097 identifier: "match_expression".to_string(),
1098 original_content: String::new(),
1099 location: NodeLocation {
1100 line: 0,
1101 column: 0,
1102 end_line: 0,
1103 end_column: 0,
1104 },
1105 }
1106 };
1107
1108 let new_body: syn::Expr = parse_str(&op.new_body)
1110 .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1111
1112 let mut visitor = MatchArmUpdater {
1114 target_function: op.function_name.clone(),
1115 pattern_to_match: op.pattern.clone(),
1116 new_body,
1117 modified: false,
1118 current_function: None,
1119 modified_function: None,
1120 };
1121
1122 visitor.visit_file_mut(&mut self.syntax_tree);
1123
1124 if visitor.modified {
1125 self.replace_modified_functions(&visitor.modified_function)?;
1127 Ok(ModificationResult {
1128 changed: true,
1129 modified_nodes: vec![backup_node],
1130 })
1131 } else {
1132 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1133 }
1134 }
1135
1136 pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1137 let backup_node = if let Some(ref fn_name) = op.function_name {
1139 self.get_function_backup(fn_name)?
1140 } else {
1141 BackupNode {
1142 node_type: "Unknown".to_string(),
1143 identifier: "match_expression".to_string(),
1144 original_content: String::new(),
1145 location: NodeLocation {
1146 line: 0,
1147 column: 0,
1148 end_line: 0,
1149 end_column: 0,
1150 },
1151 }
1152 };
1153
1154 let mut visitor = MatchArmRemover {
1156 target_function: op.function_name.clone(),
1157 pattern_to_remove: op.pattern.clone(),
1158 modified: false,
1159 current_function: None,
1160 modified_function: None,
1161 };
1162
1163 visitor.visit_file_mut(&mut self.syntax_tree);
1164
1165 if visitor.modified {
1166 self.replace_modified_functions(&visitor.modified_function)?;
1168 Ok(ModificationResult {
1169 changed: true,
1170 modified_nodes: vec![backup_node],
1171 })
1172 } else {
1173 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1174 }
1175 }
1176
1177 pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1178 let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1180 let dummy: syn::ItemImpl = parse_str(&method_code)
1181 .context("Failed to parse method definition")?;
1182
1183 let new_method = dummy.items.first()
1184 .context("No method found in definition")?
1185 .clone();
1186
1187 let method_name = match &new_method {
1189 syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1190 _ => anyhow::bail!("Only method definitions are supported"),
1191 };
1192
1193 let impl_index = self.syntax_tree.items.iter().position(|item| {
1195 if let Item::Impl(impl_block) = item {
1196 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1198 if let Some(segment) = type_path.path.segments.last() {
1199 return segment.ident == op.target;
1200 }
1201 }
1202 }
1203 false
1204 }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1205
1206 let impl_block = match &self.syntax_tree.items[impl_index] {
1208 Item::Impl(i) => i,
1209 _ => unreachable!(),
1210 };
1211
1212 let method_exists = impl_block.items.iter().any(|item| {
1213 if let syn::ImplItem::Fn(f) = item {
1214 f.sig.ident == method_name
1215 } else {
1216 false
1217 }
1218 });
1219
1220 if method_exists {
1221 return Ok(ModificationResult {
1222 changed: false,
1223 modified_nodes: vec![],
1224 });
1225 }
1226
1227 let backup_node = BackupNode {
1229 node_type: "ItemImpl".to_string(),
1230 identifier: op.target.clone(),
1231 original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1232 location: self.span_to_location(impl_block.span()),
1233 };
1234
1235 let impl_span = impl_block.span();
1237
1238 match &mut self.syntax_tree.items[impl_index] {
1240 Item::Impl(impl_block) => {
1241 match &op.position {
1243 InsertPosition::First => {
1244 impl_block.items.insert(0, new_method);
1245 }
1246 InsertPosition::Last => {
1247 impl_block.items.push(new_method);
1248 }
1249 InsertPosition::After(name) => {
1250 let pos = impl_block.items.iter().position(|item| {
1251 if let syn::ImplItem::Fn(f) = item {
1252 f.sig.ident == name
1253 } else {
1254 false
1255 }
1256 }).with_context(|| format!("Method '{}' not found", name))?;
1257 impl_block.items.insert(pos + 1, new_method);
1258 }
1259 InsertPosition::Before(name) => {
1260 let pos = impl_block.items.iter().position(|item| {
1261 if let syn::ImplItem::Fn(f) = item {
1262 f.sig.ident == name
1263 } else {
1264 false
1265 }
1266 }).with_context(|| format!("Method '{}' not found", name))?;
1267 impl_block.items.insert(pos, new_method);
1268 }
1269 }
1270 }
1271 _ => unreachable!(),
1272 }
1273
1274 self.replace_formatted_item(impl_index, impl_span)?;
1276
1277 Ok(ModificationResult {
1278 changed: true,
1279 modified_nodes: vec![backup_node],
1280 })
1281 }
1282
1283 pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1284 let use_code = format!("use {};", op.use_path);
1286 let use_item: syn::ItemUse = parse_str(&use_code)
1287 .context("Failed to parse use statement")?;
1288
1289 let use_exists = self.syntax_tree.items.iter().any(|item| {
1291 if let Item::Use(existing_use) = item {
1292 existing_use.tree.to_token_stream().to_string() ==
1294 use_item.tree.to_token_stream().to_string()
1295 } else {
1296 false
1297 }
1298 });
1299
1300 if use_exists {
1301 return Ok(ModificationResult {
1302 changed: false,
1303 modified_nodes: vec![],
1304 });
1305 }
1306
1307 let backup_node = BackupNode {
1309 node_type: "ItemUse".to_string(),
1310 identifier: op.use_path.clone(),
1311 original_content: format!("use {};", op.use_path),
1312 location: NodeLocation {
1313 line: 0,
1314 column: 0,
1315 end_line: 0,
1316 end_column: 0,
1317 },
1318 };
1319
1320 let insert_index = match &op.position {
1322 InsertPosition::First => 0,
1323 InsertPosition::Last => {
1324 self.syntax_tree.items.iter()
1326 .rposition(|item| matches!(item, Item::Use(_)))
1327 .map(|i| i + 1)
1328 .unwrap_or(0)
1329 }
1330 InsertPosition::After(path) => {
1331 let pos = self.syntax_tree.items.iter().position(|item| {
1333 if let Item::Use(u) = item {
1334 u.tree.to_token_stream().to_string().contains(path)
1335 } else {
1336 false
1337 }
1338 }).with_context(|| format!("Use statement for '{}' not found", path))?;
1339 pos + 1
1340 }
1341 InsertPosition::Before(path) => {
1342 self.syntax_tree.items.iter().position(|item| {
1344 if let Item::Use(u) = item {
1345 u.tree.to_token_stream().to_string().contains(path)
1346 } else {
1347 false
1348 }
1349 }).with_context(|| format!("Use statement for '{}' not found", path))?
1350 }
1351 };
1352
1353 self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1355
1356 let insert_line_pos = if insert_index == 0 {
1359 0
1361 } else {
1362 let prev_item = &self.syntax_tree.items[insert_index - 1];
1364 let span = prev_item.span();
1365 let end_pos = self.span_to_byte_offset(span.end());
1366
1367 let mut line_end = end_pos;
1369 while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1370 line_end += 1;
1371 }
1372 if line_end < self.content.len() {
1374 line_end + 1
1375 } else {
1376 self.content.push('\n');
1378 self.content.len()
1379 }
1380 };
1381
1382 let use_str = format!("use {};\n", op.use_path);
1384
1385 self.content.insert_str(insert_line_pos, &use_str);
1387
1388 Ok(ModificationResult {
1389 changed: true,
1390 modified_nodes: vec![backup_node],
1391 })
1392 }
1393
1394 pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1395 let item_index = self.syntax_tree.items.iter().position(|item| {
1397 match (&op.target_type as &str, item) {
1398 ("struct", Item::Struct(s)) => s.ident == op.target_name,
1399 ("enum", Item::Enum(e)) => e.ident == op.target_name,
1400 _ => false,
1401 }
1402 }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1403
1404 let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1406 Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1407 Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1408 _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1409 };
1410
1411 if let Some(ref where_filter) = op.where_filter {
1413 if !self.matches_where_filter(item_attrs, where_filter)? {
1414 return Ok(ModificationResult {
1416 changed: false,
1417 modified_nodes: vec![],
1418 });
1419 }
1420 }
1421
1422 let backup_node = BackupNode {
1424 node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1425 identifier: op.target_name.clone(),
1426 original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1427 location: self.span_to_location(item_span),
1428 };
1429
1430 let new_derives: Vec<String> = op.derives.iter()
1432 .filter(|d| !existing_derives.contains(&d.to_string()))
1433 .cloned()
1434 .collect();
1435
1436 if new_derives.is_empty() {
1437 return Ok(ModificationResult {
1439 changed: false,
1440 modified_nodes: vec![],
1441 });
1442 }
1443
1444 let mut all_derives = existing_derives;
1446 all_derives.extend(new_derives);
1447
1448 let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1450
1451 match &mut self.syntax_tree.items[item_index] {
1453 Item::Struct(s) => {
1454 Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1455 }
1456 Item::Enum(e) => {
1457 Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1458 }
1459 _ => unreachable!(),
1460 }
1461
1462 self.replace_formatted_item(item_index, item_span)?;
1464
1465 Ok(ModificationResult {
1466 changed: true,
1467 modified_nodes: vec![backup_node],
1468 })
1469 }
1470
1471 fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1473 let item_start_pos = self.span_to_byte_offset(original_span.start());
1475 let item_end_pos = self.span_to_byte_offset(original_span.end());
1476
1477 let mut actual_start = item_start_pos;
1479
1480 let mut temp_pos = item_start_pos;
1482 while temp_pos > 0 {
1483 temp_pos = temp_pos.saturating_sub(1);
1485 let mut line_start = temp_pos;
1486 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1487 line_start -= 1;
1488 }
1489
1490 let line = if temp_pos < self.content.len() {
1491 &self.content[line_start..temp_pos + 1]
1492 } else {
1493 &self.content[line_start..]
1494 };
1495 let trimmed = line.trim();
1496
1497 if trimmed.starts_with("#[") {
1498 actual_start = line_start;
1499 temp_pos = line_start;
1500 } else if trimmed.is_empty() {
1501 temp_pos = line_start;
1502 } else {
1503 break;
1504 }
1505
1506 if line_start == 0 {
1507 break;
1508 }
1509 }
1510
1511 let item_clone = self.syntax_tree.items[item_index].clone();
1513 let temp_file = syn::File {
1514 shebang: None,
1515 attrs: Vec::new(),
1516 items: vec![item_clone],
1517 };
1518
1519 let formatted = prettyplease::unparse(&temp_file);
1521 let formatted = formatted.trim();
1522
1523 self.content.replace_range(actual_start..item_end_pos, formatted);
1525
1526 Ok(())
1527 }
1528
1529 fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1531 for attr in attrs {
1532 if attr.path().is_ident("derive") {
1533 if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1534 let tokens_str = meta_list.tokens.to_string();
1535 return tokens_str
1536 .split(',')
1537 .map(|s| s.trim().to_string())
1538 .collect();
1539 }
1540 }
1541 }
1542 Vec::new()
1543 }
1544
1545 fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1550 if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1552 let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1553 let existing_derives = Self::extract_derives(attrs);
1554
1555 for required_trait in required_traits {
1557 if existing_derives.iter().any(|d| d == required_trait) {
1558 return Ok(true);
1559 }
1560 }
1561 return Ok(false);
1562 }
1563
1564 Ok(true)
1566 }
1567
1568 fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1570 let derive_str = derives.join(", ");
1571
1572 let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1574 let parsed: syn::ItemStruct = parse_str(&dummy)
1575 .context("Failed to parse derive attribute")?;
1576
1577 let new_attr = parsed.attrs.into_iter()
1578 .find(|a| a.path().is_ident("derive"))
1579 .context("Failed to extract derive attribute")?;
1580
1581 if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1583 attrs[pos] = new_attr;
1584 } else {
1585 attrs.insert(0, new_attr);
1587 }
1588
1589 Ok(())
1590 }
1591
1592 fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1594 if modified_function.is_none() {
1596 self.content = prettyplease::unparse(&self.syntax_tree);
1597 return Ok(());
1598 }
1599
1600 let original_syntax_tree: File = syn::parse_str(&self.content)
1602 .context("Failed to re-parse original content")?;
1603
1604 let function_name = modified_function.as_ref().unwrap();
1605
1606 let original_fn = original_syntax_tree.items.iter()
1608 .find_map(|item| {
1609 if let Item::Fn(f) = item {
1610 if f.sig.ident == function_name {
1611 return Some(f.clone());
1612 }
1613 }
1614 None
1615 })
1616 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1617
1618 let start = self.span_to_byte_offset(original_fn.span().start());
1620 let end = self.span_to_byte_offset(original_fn.span().end());
1621
1622 let modified_fn = self.syntax_tree.items.iter()
1624 .find_map(|item| {
1625 if let Item::Fn(f) = item {
1626 if f.sig.ident == function_name {
1627 return Some(f.clone());
1628 }
1629 }
1630 None
1631 })
1632 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1633
1634 let dummy_file = syn::File {
1636 shebang: None,
1637 attrs: Vec::new(),
1638 items: vec![Item::Fn(modified_fn)],
1639 };
1640
1641 let formatted_fn = prettyplease::unparse(&dummy_file);
1642
1643 let formatted_fn = formatted_fn.trim();
1645
1646 self.content.replace_range(start..end, formatted_fn);
1648
1649 Ok(())
1650 }
1651
1652 fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1653 let line_idx = pos.line.saturating_sub(1);
1654 if line_idx < self.line_offsets.len() {
1655 self.line_offsets[line_idx] + pos.column
1656 } else {
1657 self.content.len()
1658 }
1659 }
1660
1661 fn find_after_field_end(&self, pos: usize) -> usize {
1662 let mut i = pos;
1664 while i < self.content.len() {
1665 match self.content.as_bytes()[i] as char {
1666 ',' => return i + 1,
1667 '\n' => return i + 1,
1668 _ => i += 1,
1669 }
1670 }
1671 pos
1672 }
1673
1674 fn get_indentation(&self, pos: usize) -> String {
1675 let mut line_start = pos;
1677 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1678 line_start -= 1;
1679 }
1680
1681 let mut indent = String::new();
1683 let mut i = line_start;
1684 while i < self.content.len() {
1685 match self.content.as_bytes()[i] as char {
1686 ' ' | '\t' => {
1687 indent.push(self.content.as_bytes()[i] as char);
1688 i += 1;
1689 }
1690 _ => break,
1691 }
1692 }
1693
1694 if indent.is_empty() {
1696 " ".to_string()
1697 } else {
1698 indent
1699 }
1700 }
1701
1702 pub fn to_string(&self) -> String {
1703 self.content.clone()
1704 }
1705
1706 pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1708 use syn::visit::Visit;
1709 use crate::operations::InspectResult;
1710
1711 let mut results = Vec::new();
1712
1713 match node_type {
1714 "struct-literal" => {
1715 struct StructLiteralVisitor<'a> {
1717 results: &'a mut Vec<InspectResult>,
1718 name_filter: Option<&'a str>,
1719 editor: &'a RustEditor,
1720 }
1721
1722 impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1723 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1724 let filter = match self.name_filter {
1730 Some(f) => f,
1731 None => {
1732 let struct_name = node.path.segments.last()
1734 .map(|seg| seg.ident.to_string())
1735 .unwrap_or_default();
1736
1737 let snippet = self.editor.format_expr_struct(node);
1738 let location = self.editor.span_to_location(node.span());
1739
1740 self.results.push(InspectResult {
1741 file_path: String::new(),
1742 node_type: "ExprStruct".to_string(),
1743 identifier: struct_name,
1744 location,
1745 snippet,
1746 });
1747
1748 syn::visit::visit_expr_struct(self, node);
1749 return;
1750 }
1751 };
1752
1753 let matches = if filter.contains("::") {
1755 if filter.starts_with("*::") {
1757 let target_name = &filter[3..]; node.path.segments.last()
1760 .map(|seg| seg.ident.to_string() == target_name)
1761 .unwrap_or(false)
1762 } else {
1763 let path_str = node.path.segments.iter()
1765 .map(|seg| seg.ident.to_string())
1766 .collect::<Vec<_>>()
1767 .join("::");
1768 path_str == filter
1769 }
1770 } else {
1771 node.path.get_ident()
1773 .map(|ident| ident.to_string() == filter)
1774 .unwrap_or(false)
1775 };
1776
1777 if !matches {
1778 syn::visit::visit_expr_struct(self, node);
1779 return;
1780 }
1781
1782 let struct_name = node.path.segments.last()
1784 .map(|seg| seg.ident.to_string())
1785 .unwrap_or_default();
1786
1787 let snippet = self.editor.format_expr_struct(node);
1789 let location = self.editor.span_to_location(node.span());
1790
1791 self.results.push(InspectResult {
1792 file_path: String::new(), node_type: "ExprStruct".to_string(),
1794 identifier: struct_name,
1795 location,
1796 snippet,
1797 });
1798
1799 syn::visit::visit_expr_struct(self, node);
1801 }
1802 }
1803
1804 let mut visitor = StructLiteralVisitor {
1805 results: &mut results,
1806 name_filter,
1807 editor: self,
1808 };
1809
1810 for item in &self.syntax_tree.items {
1812 syn::visit::visit_item(&mut visitor, item);
1813 }
1814 }
1815 "match-arm" => {
1816 struct MatchArmVisitor<'a> {
1818 results: &'a mut Vec<InspectResult>,
1819 pattern_filter: Option<&'a str>,
1820 editor: &'a RustEditor,
1821 }
1822
1823 impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1824 fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1825 for arm in &node.arms {
1827 let pat = &arm.pat;
1829 let pattern_str = quote::quote!(#pat).to_string();
1830
1831 if let Some(filter) = self.pattern_filter {
1833 let normalized_pattern = pattern_str.replace(" ", "");
1835 let normalized_filter = filter.replace(" ", "");
1836
1837 if !normalized_pattern.contains(&normalized_filter) {
1838 continue;
1839 }
1840 }
1841
1842 let snippet = self.editor.format_match_arm(arm);
1844 let location = self.editor.span_to_location(arm.span());
1845
1846 self.results.push(InspectResult {
1847 file_path: String::new(), node_type: "MatchArm".to_string(),
1849 identifier: pattern_str.replace(" ", ""),
1850 location,
1851 snippet,
1852 });
1853 }
1854
1855 syn::visit::visit_expr_match(self, node);
1857 }
1858 }
1859
1860 let mut visitor = MatchArmVisitor {
1861 results: &mut results,
1862 pattern_filter: name_filter,
1863 editor: self,
1864 };
1865
1866 for item in &self.syntax_tree.items {
1868 syn::visit::visit_item(&mut visitor, item);
1869 }
1870 }
1871 "enum-usage" => {
1872 struct EnumUsageVisitor<'a> {
1874 results: &'a mut Vec<InspectResult>,
1875 path_filter: Option<&'a str>,
1876 editor: &'a RustEditor,
1877 }
1878
1879 impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1880 fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1881 let path = &node.path;
1883 let path_str = quote::quote!(#path).to_string();
1884
1885 if let Some(filter) = self.path_filter {
1887 let normalized_path = path_str.replace(" ", "");
1889 let normalized_filter = filter.replace(" ", "");
1890
1891 if !normalized_path.contains(&normalized_filter) {
1892 syn::visit::visit_expr_path(self, node);
1893 return;
1894 }
1895 }
1896
1897 let snippet = self.editor.format_expr_path(node);
1899 let location = self.editor.span_to_location(node.span());
1900
1901 self.results.push(InspectResult {
1902 file_path: String::new(), node_type: "ExprPath".to_string(),
1904 identifier: path_str.replace(" ", ""),
1905 location,
1906 snippet,
1907 });
1908
1909 syn::visit::visit_expr_path(self, node);
1911 }
1912 }
1913
1914 let mut visitor = EnumUsageVisitor {
1915 results: &mut results,
1916 path_filter: name_filter,
1917 editor: self,
1918 };
1919
1920 for item in &self.syntax_tree.items {
1922 syn::visit::visit_item(&mut visitor, item);
1923 }
1924 }
1925 "function-call" => {
1926 struct FunctionCallVisitor<'a> {
1928 results: &'a mut Vec<InspectResult>,
1929 name_filter: Option<&'a str>,
1930 editor: &'a RustEditor,
1931 }
1932
1933 impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
1934 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1935 let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1937 expr_path.path.segments.last()
1939 .map(|seg| seg.ident.to_string())
1940 .unwrap_or_default()
1941 } else {
1942 quote::quote!(#node.func).to_string()
1944 };
1945
1946 if let Some(filter) = self.name_filter {
1948 if func_name != filter {
1949 syn::visit::visit_expr_call(self, node);
1950 return;
1951 }
1952 }
1953
1954 let snippet = self.editor.format_expr_call(node);
1956 let location = self.editor.span_to_location(node.span());
1957
1958 self.results.push(InspectResult {
1959 file_path: String::new(), node_type: "ExprCall".to_string(),
1961 identifier: func_name,
1962 location,
1963 snippet,
1964 });
1965
1966 syn::visit::visit_expr_call(self, node);
1968 }
1969 }
1970
1971 let mut visitor = FunctionCallVisitor {
1972 results: &mut results,
1973 name_filter,
1974 editor: self,
1975 };
1976
1977 for item in &self.syntax_tree.items {
1979 syn::visit::visit_item(&mut visitor, item);
1980 }
1981 }
1982 "method-call" => {
1983 struct MethodCallVisitor<'a> {
1985 results: &'a mut Vec<InspectResult>,
1986 name_filter: Option<&'a str>,
1987 editor: &'a RustEditor,
1988 }
1989
1990 impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1991 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1992 let method_name = node.method.to_string();
1994
1995 if let Some(filter) = self.name_filter {
1997 if method_name != filter {
1998 syn::visit::visit_expr_method_call(self, node);
1999 return;
2000 }
2001 }
2002
2003 let snippet = self.editor.format_expr_method_call(node);
2005 let location = self.editor.span_to_location(node.span());
2006
2007 self.results.push(InspectResult {
2008 file_path: String::new(), node_type: "ExprMethodCall".to_string(),
2010 identifier: method_name,
2011 location,
2012 snippet,
2013 });
2014
2015 syn::visit::visit_expr_method_call(self, node);
2017 }
2018 }
2019
2020 let mut visitor = MethodCallVisitor {
2021 results: &mut results,
2022 name_filter,
2023 editor: self,
2024 };
2025
2026 for item in &self.syntax_tree.items {
2028 syn::visit::visit_item(&mut visitor, item);
2029 }
2030 }
2031 "identifier" => {
2032 struct IdentifierVisitor<'a> {
2034 results: &'a mut Vec<InspectResult>,
2035 name_filter: Option<&'a str>,
2036 editor: &'a RustEditor,
2037 }
2038
2039 impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2040 fn visit_ident(&mut self, node: &'ast syn::Ident) {
2041 let ident_name = node.to_string();
2043
2044 if let Some(filter) = self.name_filter {
2046 if ident_name != filter {
2047 syn::visit::visit_ident(self, node);
2048 return;
2049 }
2050 }
2051
2052 let snippet = self.editor.format_ident(node);
2054 let location = self.editor.span_to_location(node.span());
2055
2056 self.results.push(InspectResult {
2057 file_path: String::new(), node_type: "Ident".to_string(),
2059 identifier: ident_name,
2060 location,
2061 snippet,
2062 });
2063
2064 syn::visit::visit_ident(self, node);
2066 }
2067 }
2068
2069 let mut visitor = IdentifierVisitor {
2070 results: &mut results,
2071 name_filter,
2072 editor: self,
2073 };
2074
2075 for item in &self.syntax_tree.items {
2077 syn::visit::visit_item(&mut visitor, item);
2078 }
2079 }
2080 "type-ref" => {
2081 struct TypeRefVisitor<'a> {
2083 results: &'a mut Vec<InspectResult>,
2084 name_filter: Option<&'a str>,
2085 editor: &'a RustEditor,
2086 }
2087
2088 impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2089 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2090 let type_name = node.path.segments.last()
2092 .map(|seg| seg.ident.to_string())
2093 .unwrap_or_default();
2094
2095 if let Some(filter) = self.name_filter {
2097 if type_name != filter {
2098 syn::visit::visit_type_path(self, node);
2099 return;
2100 }
2101 }
2102
2103 let snippet = self.editor.format_type_path(node);
2105 let location = self.editor.span_to_location(node.span());
2106
2107 let path = &node.path;
2109 let path_str = quote::quote!(#path).to_string();
2110
2111 self.results.push(InspectResult {
2112 file_path: String::new(), node_type: "TypePath".to_string(),
2114 identifier: path_str.replace(" ", ""),
2115 location,
2116 snippet,
2117 });
2118
2119 syn::visit::visit_type_path(self, node);
2121 }
2122 }
2123
2124 let mut visitor = TypeRefVisitor {
2125 results: &mut results,
2126 name_filter,
2127 editor: self,
2128 };
2129
2130 for item in &self.syntax_tree.items {
2132 syn::visit::visit_item(&mut visitor, item);
2133 }
2134 }
2135 "macro-call" => {
2136 struct MacroCallVisitor<'a> {
2138 results: &'a mut Vec<InspectResult>,
2139 name_filter: Option<&'a str>,
2140 editor: &'a RustEditor,
2141 }
2142
2143 impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2144 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2145 let macro_name = node.mac.path.segments.last()
2147 .map(|seg| seg.ident.to_string())
2148 .unwrap_or_default();
2149
2150 if let Some(filter) = self.name_filter {
2152 if macro_name != filter {
2153 syn::visit::visit_expr_macro(self, node);
2154 return;
2155 }
2156 }
2157
2158 let snippet = self.editor.format_expr_macro(node);
2160 let location = self.editor.span_to_location(node.span());
2161
2162 self.results.push(InspectResult {
2163 file_path: String::new(), node_type: "ExprMacro".to_string(),
2165 identifier: macro_name,
2166 location,
2167 snippet,
2168 });
2169
2170 syn::visit::visit_expr_macro(self, node);
2172 }
2173
2174 fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2175 if let syn::Stmt::Macro(macro_stmt) = node {
2177 let macro_name = macro_stmt.mac.path.segments.last()
2178 .map(|seg| seg.ident.to_string())
2179 .unwrap_or_default();
2180
2181 if let Some(filter) = self.name_filter {
2183 if macro_name != filter {
2184 syn::visit::visit_stmt(self, node);
2185 return;
2186 }
2187 }
2188
2189 let snippet = self.editor.format_stmt_macro(macro_stmt);
2191 let location = self.editor.span_to_location(macro_stmt.span());
2192
2193 self.results.push(InspectResult {
2194 file_path: String::new(), node_type: "StmtMacro".to_string(),
2196 identifier: macro_name,
2197 location,
2198 snippet,
2199 });
2200 }
2201
2202 syn::visit::visit_stmt(self, node);
2204 }
2205 }
2206
2207 let mut visitor = MacroCallVisitor {
2208 results: &mut results,
2209 name_filter,
2210 editor: self,
2211 };
2212
2213 for item in &self.syntax_tree.items {
2215 syn::visit::visit_item(&mut visitor, item);
2216 }
2217 }
2218 _ => anyhow::bail!("Unsupported node type: {}", node_type),
2219 }
2220
2221 Ok(results)
2222 }
2223
2224 fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
2226 let start = self.span_to_byte_offset(expr.span().start());
2228 let end = self.span_to_byte_offset(expr.span().end());
2229
2230 let original = &self.content[start..end];
2232
2233 original.split_whitespace().collect::<Vec<_>>().join(" ")
2235 }
2236
2237 fn format_match_arm(&self, arm: &syn::Arm) -> String {
2239 let start = self.span_to_byte_offset(arm.span().start());
2241 let end = self.span_to_byte_offset(arm.span().end());
2242
2243 let original = &self.content[start..end];
2245
2246 original.split_whitespace().collect::<Vec<_>>().join(" ")
2248 }
2249
2250 fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2252 let start = self.span_to_byte_offset(expr.span().start());
2254 let end = self.span_to_byte_offset(expr.span().end());
2255
2256 let original = &self.content[start..end];
2258
2259 original.split_whitespace().collect::<Vec<_>>().join(" ")
2261 }
2262
2263 fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
2265 let start = self.span_to_byte_offset(expr.span().start());
2267 let end = self.span_to_byte_offset(expr.span().end());
2268
2269 let original = &self.content[start..end];
2271
2272 original.split_whitespace().collect::<Vec<_>>().join(" ")
2274 }
2275
2276 fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2278 let start = self.span_to_byte_offset(expr.span().start());
2280 let end = self.span_to_byte_offset(expr.span().end());
2281
2282 let original = &self.content[start..end];
2284
2285 original.split_whitespace().collect::<Vec<_>>().join(" ")
2287 }
2288
2289 fn format_ident(&self, ident: &syn::Ident) -> String {
2291 ident.to_string()
2292 }
2293
2294 fn format_type_path(&self, ty: &syn::TypePath) -> String {
2296 let start = self.span_to_byte_offset(ty.span().start());
2298 let end = self.span_to_byte_offset(ty.span().end());
2299
2300 let original = &self.content[start..end];
2302
2303 original.split_whitespace().collect::<Vec<_>>().join(" ")
2305 }
2306
2307 fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
2309 let start = self.span_to_byte_offset(expr.span().start());
2311 let end = self.span_to_byte_offset(expr.span().end());
2312
2313 let original = &self.content[start..end];
2315
2316 original.split_whitespace().collect::<Vec<_>>().join(" ")
2318 }
2319
2320 fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
2322 let start = self.span_to_byte_offset(stmt.span().start());
2324 let end = self.span_to_byte_offset(stmt.span().end());
2325
2326 let original = &self.content[start..end];
2328
2329 original.split_whitespace().collect::<Vec<_>>().join(" ")
2331 }
2332
2333 #[allow(dead_code)]
2335 pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2336 for (index, item) in self.syntax_tree.items.iter().enumerate() {
2337 match (node_type, item) {
2338 ("struct", Item::Struct(s)) if s.ident == name => {
2339 return Ok(index);
2340 }
2341 ("enum", Item::Enum(e)) if e.ident == name => {
2342 return Ok(index);
2343 }
2344 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2345 return Ok(index);
2346 }
2347 ("impl", Item::Impl(impl_block)) => {
2348 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2350 if let Some(segment) = type_path.path.segments.last() {
2351 if segment.ident == name {
2352 return Ok(index);
2353 }
2354 }
2355 }
2356 }
2357 _ => {}
2358 }
2359 }
2360
2361 anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2362 }
2363
2364 #[allow(dead_code)]
2366 pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2367 if index >= self.syntax_tree.items.len() {
2368 anyhow::bail!("Index {} out of bounds", index);
2369 }
2370
2371 self.syntax_tree.items[index] = new_item;
2373
2374 self.content = prettyplease::unparse(&self.syntax_tree);
2376
2377 self.line_offsets = Self::compute_line_offsets(&self.content);
2379
2380 Ok(())
2381 }
2382
2383 pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2384 let mut locations = Vec::new();
2385
2386 for item in &self.syntax_tree.items {
2387 match (node_type, item) {
2388 ("struct", Item::Struct(s)) if s.ident == name => {
2389 locations.push(self.span_to_location(s.span()));
2390 }
2391 ("enum", Item::Enum(e)) if e.ident == name => {
2392 locations.push(self.span_to_location(e.span()));
2393 }
2394 ("fn", Item::Fn(f)) if f.sig.ident == name => {
2395 locations.push(self.span_to_location(f.span()));
2396 }
2397 _ => {}
2398 }
2399 }
2400
2401 if locations.is_empty() {
2402 anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2403 }
2404
2405 Ok(locations)
2406 }
2407
2408 fn span_to_location(&self, span: Span) -> NodeLocation {
2409 let start = span.start();
2410 let end = span.end();
2411
2412 NodeLocation {
2413 line: start.line,
2414 column: start.column,
2415 end_line: end.line,
2416 end_column: end.column,
2417 }
2418 }
2419
2420 pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
2422 use crate::operations::{InspectResult, TransformAction};
2423
2424 let matches = self.inspect(&op.node_type, op.name_filter.as_deref())?;
2426
2427 let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
2429 matches.into_iter()
2430 .filter(|m| m.snippet.contains(content_filter))
2431 .collect()
2432 } else {
2433 matches
2434 };
2435
2436 if filtered_matches.is_empty() {
2437 return Ok(ModificationResult {
2438 changed: false,
2439 modified_nodes: vec![],
2440 });
2441 }
2442
2443 let mut sorted_matches = filtered_matches;
2446 sorted_matches.sort_by(|a, b| {
2447 b.location.line.cmp(&a.location.line)
2448 .then(b.location.column.cmp(&a.location.column))
2449 });
2450
2451 let mut modified_nodes = Vec::new();
2452
2453 for match_result in &sorted_matches {
2454 let backup_node = BackupNode {
2456 node_type: match_result.node_type.clone(),
2457 identifier: match_result.identifier.clone(),
2458 original_content: match_result.snippet.clone(),
2459 location: match_result.location.clone(),
2460 };
2461
2462 let start_offset = self.line_column_to_byte_offset(
2464 match_result.location.line,
2465 match_result.location.column
2466 )?;
2467 let end_offset = self.line_column_to_byte_offset(
2468 match_result.location.end_line,
2469 match_result.location.end_column
2470 )?;
2471
2472 let original_text = &self.content[start_offset..end_offset];
2474
2475 let replacement = match &op.action {
2477 TransformAction::Comment => {
2478 format!("// {}", original_text.replace("\n", "\n// "))
2480 }
2481 TransformAction::Remove => {
2482 String::new()
2484 }
2485 TransformAction::Replace { with } => {
2486 with.clone()
2488 }
2489 };
2490
2491 self.content.replace_range(start_offset..end_offset, &replacement);
2493
2494 self.line_offsets = Self::compute_line_offsets(&self.content);
2496
2497 modified_nodes.push(backup_node);
2498 }
2499
2500 if !modified_nodes.is_empty() {
2502 }
2506
2507 Ok(ModificationResult {
2508 changed: !modified_nodes.is_empty(),
2509 modified_nodes,
2510 })
2511 }
2512
2513 pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
2515 use crate::operations::EditMode;
2516
2517 let path_resolver = if let Some(enum_path) = &op.enum_path {
2519 let mut resolver = PathResolver::new(enum_path)
2520 .ok_or_else(|| anyhow::anyhow!("Invalid enum path: {}", enum_path))?;
2521
2522 resolver.scan_file(&self.syntax_tree);
2524 Some(resolver)
2525 } else {
2526 None
2527 };
2528
2529 match op.edit_mode {
2530 EditMode::Surgical => {
2531 use syn::visit::Visit;
2533 use crate::surgical::Replacement;
2534
2535 let mut collector = EnumVariantReplacementCollector {
2536 enum_name: op.enum_name.clone(),
2537 old_variant: op.old_variant.clone(),
2538 new_variant: op.new_variant.clone(),
2539 path_resolver,
2540 replacements: Vec::new(),
2541 };
2542
2543 collector.visit_file(&self.syntax_tree);
2544
2545 if collector.replacements.is_empty() {
2546 return Ok(ModificationResult {
2547 changed: false,
2548 modified_nodes: vec![],
2549 });
2550 }
2551
2552 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
2554
2555 self.line_offsets = Self::compute_line_offsets(&self.content);
2557
2558 self.syntax_tree = syn::parse_str(&self.content)
2560 .context("Failed to re-parse after surgical edit")?;
2561
2562 let backup_node = BackupNode {
2563 node_type: "EnumVariantRename".to_string(),
2564 identifier: format!("{}::{} -> {} (surgical)", op.enum_name, op.old_variant, op.new_variant),
2565 original_content: format!("Renamed {} to {} in enum {} (surgical mode)", op.old_variant, op.new_variant, op.enum_name),
2566 location: NodeLocation {
2567 line: 1,
2568 column: 0,
2569 end_line: 1,
2570 end_column: 0,
2571 },
2572 };
2573
2574 Ok(ModificationResult {
2575 changed: true,
2576 modified_nodes: vec![backup_node],
2577 })
2578 }
2579 EditMode::Reformat => {
2580 let mut renamer = EnumVariantRenamer {
2582 enum_name: op.enum_name.clone(),
2583 old_variant: op.old_variant.clone(),
2584 new_variant: op.new_variant.clone(),
2585 path_resolver,
2586 modified: false,
2587 };
2588
2589 renamer.visit_file_mut(&mut self.syntax_tree);
2591
2592 if !renamer.modified {
2593 return Ok(ModificationResult {
2594 changed: false,
2595 modified_nodes: vec![],
2596 });
2597 }
2598
2599 self.content = prettyplease::unparse(&self.syntax_tree);
2601
2602 self.line_offsets = Self::compute_line_offsets(&self.content);
2604
2605 let backup_node = BackupNode {
2607 node_type: "EnumVariantRename".to_string(),
2608 identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
2609 original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
2610 location: NodeLocation {
2611 line: 1,
2612 column: 0,
2613 end_line: 1,
2614 end_column: 0,
2615 },
2616 };
2617
2618 Ok(ModificationResult {
2619 changed: true,
2620 modified_nodes: vec![backup_node],
2621 })
2622 }
2623 }
2624 }
2625
2626 pub(crate) fn rename_function(&mut self, op: &crate::operations::RenameFunctionOp) -> Result<ModificationResult> {
2628 use crate::operations::EditMode;
2629
2630 let path_resolver = if let Some(function_path) = &op.function_path {
2632 let mut resolver = PathResolver::new(function_path)
2633 .ok_or_else(|| anyhow::anyhow!("Invalid function path: {}", function_path))?;
2634
2635 resolver.scan_file(&self.syntax_tree);
2637 Some(resolver)
2638 } else {
2639 None
2640 };
2641
2642 match op.edit_mode {
2643 EditMode::Surgical => {
2644 use syn::visit::Visit;
2646
2647 let mut collector = FunctionReplacementCollector {
2648 old_name: op.old_name.clone(),
2649 new_name: op.new_name.clone(),
2650 path_resolver,
2651 replacements: Vec::new(),
2652 };
2653
2654 collector.visit_file(&self.syntax_tree);
2655
2656 if collector.replacements.is_empty() {
2657 return Ok(ModificationResult {
2658 changed: false,
2659 modified_nodes: vec![],
2660 });
2661 }
2662
2663 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
2665
2666 self.line_offsets = Self::compute_line_offsets(&self.content);
2668
2669 self.syntax_tree = syn::parse_str(&self.content)
2671 .context("Failed to re-parse after surgical edit")?;
2672
2673 let backup_node = BackupNode {
2674 node_type: "FunctionRename".to_string(),
2675 identifier: format!("{} -> {} (surgical)", op.old_name, op.new_name),
2676 original_content: format!("Renamed {} to {} (surgical mode)", op.old_name, op.new_name),
2677 location: NodeLocation {
2678 line: 1,
2679 column: 0,
2680 end_line: 1,
2681 end_column: 0,
2682 },
2683 };
2684
2685 Ok(ModificationResult {
2686 changed: true,
2687 modified_nodes: vec![backup_node],
2688 })
2689 }
2690 EditMode::Reformat => {
2691 let mut renamer = FunctionRenamer {
2693 old_name: op.old_name.clone(),
2694 new_name: op.new_name.clone(),
2695 path_resolver,
2696 modified: false,
2697 };
2698
2699 renamer.visit_file_mut(&mut self.syntax_tree);
2701
2702 if !renamer.modified {
2703 return Ok(ModificationResult {
2704 changed: false,
2705 modified_nodes: vec![],
2706 });
2707 }
2708
2709 self.content = prettyplease::unparse(&self.syntax_tree);
2711
2712 self.line_offsets = Self::compute_line_offsets(&self.content);
2714
2715 let backup_node = BackupNode {
2717 node_type: "FunctionRename".to_string(),
2718 identifier: format!("{} -> {}", op.old_name, op.new_name),
2719 original_content: format!("Renamed {} to {}", op.old_name, op.new_name),
2720 location: NodeLocation {
2721 line: 1,
2722 column: 0,
2723 end_line: 1,
2724 end_column: 0,
2725 },
2726 };
2727
2728 Ok(ModificationResult {
2729 changed: true,
2730 modified_nodes: vec![backup_node],
2731 })
2732 }
2733 }
2734 }
2735
2736 fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
2738 if line == 0 || line > self.line_offsets.len() {
2739 anyhow::bail!("Line {} out of range", line);
2740 }
2741
2742 let line_start = self.line_offsets[line - 1];
2743 Ok(line_start + column)
2744 }
2745}
2746
2747struct MatchArmAdder {
2749 target_function: Option<String>,
2750 arm_to_add: Arm,
2751 modified: bool,
2752 current_function: Option<String>,
2753 modified_function: Option<String>,
2754}
2755
2756impl VisitMut for MatchArmAdder {
2757 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2758 let prev_fn = self.current_function.clone();
2759 self.current_function = Some(node.sig.ident.to_string());
2760
2761 syn::visit_mut::visit_item_fn_mut(self, node);
2763
2764 self.current_function = prev_fn;
2765 }
2766
2767 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2768 if let Some(ref target) = self.target_function {
2770 if self.current_function.as_ref() != Some(target) {
2771 syn::visit_mut::visit_expr_match_mut(self, node);
2773 return;
2774 }
2775 }
2776
2777 let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2779 let already_exists = node.arms.iter().any(|arm| {
2780 arm.pat.to_token_stream().to_string() == pattern_str
2781 });
2782
2783 if !already_exists {
2784 node.arms.push(self.arm_to_add.clone());
2786 self.modified = true;
2787 self.modified_function = self.current_function.clone();
2788 }
2789
2790 syn::visit_mut::visit_expr_match_mut(self, node);
2792 }
2793}
2794
2795struct MatchArmUpdater {
2797 target_function: Option<String>,
2798 pattern_to_match: String,
2799 new_body: syn::Expr,
2800 modified: bool,
2801 current_function: Option<String>,
2802 modified_function: Option<String>,
2803}
2804
2805impl VisitMut for MatchArmUpdater {
2806 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2807 let prev_fn = self.current_function.clone();
2808 self.current_function = Some(node.sig.ident.to_string());
2809
2810 syn::visit_mut::visit_item_fn_mut(self, node);
2811
2812 self.current_function = prev_fn;
2813 }
2814
2815 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2816 if let Some(ref target) = self.target_function {
2818 if self.current_function.as_ref() != Some(target) {
2819 syn::visit_mut::visit_expr_match_mut(self, node);
2820 return;
2821 }
2822 }
2823
2824 for arm in &mut node.arms {
2826 let pattern_str = arm.pat.to_token_stream().to_string();
2827 let pattern_normalized = pattern_str.replace(" ", "");
2829 let target_normalized = self.pattern_to_match.replace(" ", "");
2830
2831 if pattern_normalized == target_normalized {
2832 arm.body = Box::new(self.new_body.clone());
2833 self.modified = true;
2834 self.modified_function = self.current_function.clone();
2835 break;
2836 }
2837 }
2838
2839 syn::visit_mut::visit_expr_match_mut(self, node);
2840 }
2841}
2842
2843struct MatchArmRemover {
2845 target_function: Option<String>,
2846 pattern_to_remove: String,
2847 modified: bool,
2848 current_function: Option<String>,
2849 modified_function: Option<String>,
2850}
2851
2852impl VisitMut for MatchArmRemover {
2853 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2854 let prev_fn = self.current_function.clone();
2855 self.current_function = Some(node.sig.ident.to_string());
2856
2857 syn::visit_mut::visit_item_fn_mut(self, node);
2858
2859 self.current_function = prev_fn;
2860 }
2861
2862 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2863 if let Some(ref target) = self.target_function {
2865 if self.current_function.as_ref() != Some(target) {
2866 syn::visit_mut::visit_expr_match_mut(self, node);
2867 return;
2868 }
2869 }
2870
2871 let mut index_to_remove = None;
2873 for (i, arm) in node.arms.iter().enumerate() {
2874 let pattern_str = arm.pat.to_token_stream().to_string();
2875 let pattern_normalized = pattern_str.replace(" ", "");
2877 let target_normalized = self.pattern_to_remove.replace(" ", "");
2878
2879 if pattern_normalized == target_normalized {
2880 index_to_remove = Some(i);
2881 break;
2882 }
2883 }
2884
2885 if let Some(index) = index_to_remove {
2886 node.arms.remove(index);
2887 self.modified = true;
2888 self.modified_function = self.current_function.clone();
2889 }
2890
2891 syn::visit_mut::visit_expr_match_mut(self, node);
2892 }
2893}
2894
2895struct MultiMatchArmAdder {
2897 target_function: Option<String>,
2898 arms_to_add: Vec<(String, Arm)>, modified: bool,
2900 current_function: Option<String>,
2901 modified_function: Option<String>,
2902}
2903
2904impl VisitMut for MultiMatchArmAdder {
2905 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2906 let prev_fn = self.current_function.clone();
2907 self.current_function = Some(node.sig.ident.to_string());
2908
2909 syn::visit_mut::visit_item_fn_mut(self, node);
2910
2911 self.current_function = prev_fn;
2912 }
2913
2914 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2915 if let Some(ref target) = self.target_function {
2917 if self.current_function.as_ref() != Some(target) {
2918 syn::visit_mut::visit_expr_match_mut(self, node);
2919 return;
2920 }
2921 }
2922
2923 for (pattern_str, arm) in &self.arms_to_add {
2925 let already_exists = node.arms.iter().any(|existing_arm| {
2927 existing_arm.pat.to_token_stream().to_string() == *pattern_str
2928 });
2929
2930 if !already_exists {
2931 node.arms.push(arm.clone());
2932 self.modified = true;
2933 self.modified_function = self.current_function.clone();
2934 }
2935 }
2936
2937 syn::visit_mut::visit_expr_match_mut(self, node);
2938 }
2939}
2940
2941struct StructLiteralFieldAdder {
2943 struct_name: String,
2944 field_def: String,
2945 field_name: String,
2946 position: InsertPosition,
2947 path_resolver: Option<PathResolver>,
2948 modified: bool,
2949}
2950
2951impl VisitMut for StructLiteralFieldAdder {
2952 fn visit_expr_mut(&mut self, node: &mut Expr) {
2953 if let Expr::Struct(expr_struct) = node {
2955 let is_match = if let Some(resolver) = &self.path_resolver {
2956 resolver.matches_target(&expr_struct.path)
2958 } else {
2959 if self.struct_name.contains("::") {
2965 if self.struct_name.starts_with("*::") {
2967 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
2970 .map(|seg| seg.ident.to_string() == target_name)
2971 .unwrap_or(false)
2972 } else {
2973 let path_str = expr_struct.path.segments.iter()
2975 .map(|seg| seg.ident.to_string())
2976 .collect::<Vec<_>>()
2977 .join("::");
2978 path_str == self.struct_name
2979 }
2980 } else {
2981 expr_struct.path.segments.len() == 1
2983 && expr_struct.path.segments.last()
2984 .map(|seg| seg.ident.to_string())
2985 .as_ref() == Some(&self.struct_name)
2986 }
2987 };
2988
2989 if is_match {
2990 let field_exists = expr_struct.fields.iter().any(|fv| {
2992 fv.member.to_token_stream().to_string() == self.field_name
2993 });
2994
2995 if !field_exists {
2996 let field_value_code = format!("{{ {} }}", self.field_def);
2999 if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
3000 if let Some(new_fv) = expr.fields.first() {
3001 match &self.position {
3003 InsertPosition::First => {
3004 expr_struct.fields.insert(0, new_fv.clone());
3005 self.modified = true;
3006 }
3007 InsertPosition::Last => {
3008 expr_struct.fields.push(new_fv.clone());
3009 self.modified = true;
3010 }
3011 InsertPosition::After(after_field) => {
3012 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
3014 fv.member.to_token_stream().to_string() == *after_field
3015 }) {
3016 expr_struct.fields.insert(pos + 1, new_fv.clone());
3017 self.modified = true;
3018 }
3019 }
3020 InsertPosition::Before(before_field) => {
3021 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
3023 fv.member.to_token_stream().to_string() == *before_field
3024 }) {
3025 expr_struct.fields.insert(pos, new_fv.clone());
3026 self.modified = true;
3027 }
3028 }
3029 }
3030 }
3031 }
3032 }
3033 }
3034 }
3035
3036 syn::visit_mut::visit_expr_mut(self, node);
3039 }
3040}
3041
3042struct EnumVariantRenamer {
3044 enum_name: String,
3045 old_variant: String,
3046 new_variant: String,
3047 path_resolver: Option<PathResolver>,
3048 modified: bool,
3049}
3050
3051impl EnumVariantRenamer {
3052 fn rename_path(&mut self, path: &mut syn::Path) {
3062 let segments: Vec<_> = path.segments.iter().collect();
3064 let len = segments.len();
3065
3066 if len >= 2 {
3067 let potential_variant = &segments[len - 1];
3069 let potential_enum = &segments[len - 2];
3070
3071 if potential_enum.ident == self.enum_name
3072 && potential_variant.ident == self.old_variant
3073 {
3074 if let Some(resolver) = &self.path_resolver {
3078 let enum_path = syn::Path {
3080 leading_colon: path.leading_colon,
3081 segments: path.segments.iter()
3082 .take(len - 1)
3083 .cloned()
3084 .collect(),
3085 };
3086
3087 if resolver.matches_target(&enum_path) {
3089 path.segments[len - 1].ident = syn::Ident::new(
3090 &self.new_variant,
3091 path.segments[len - 1].ident.span()
3092 );
3093 self.modified = true;
3094 }
3095 } else {
3096 if len == 2 {
3099 path.segments[1].ident = syn::Ident::new(
3100 &self.new_variant,
3101 path.segments[1].ident.span()
3102 );
3103 self.modified = true;
3104 }
3105 }
3106 }
3107 } else if len == 1 {
3108 if segments[0].ident == self.old_variant {
3110 if self.path_resolver.is_none() {
3114 path.segments[0].ident = syn::Ident::new(
3115 &self.new_variant,
3116 path.segments[0].ident.span()
3117 );
3118 self.modified = true;
3119 }
3120 }
3121 }
3122 }
3123}
3124
3125impl VisitMut for EnumVariantRenamer {
3126 fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
3128 if node.ident == self.enum_name {
3129 for variant in &mut node.variants {
3130 if variant.ident == self.old_variant {
3131 variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
3132 self.modified = true;
3133 }
3134 }
3135 }
3136
3137 syn::visit_mut::visit_item_enum_mut(self, node);
3139 }
3140
3141 fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
3143 match pat {
3144 syn::Pat::TupleStruct(tuple_struct) => {
3145 self.rename_path(&mut tuple_struct.path);
3146 }
3147 syn::Pat::Struct(struct_pat) => {
3148 self.rename_path(&mut struct_pat.path);
3149 }
3150 syn::Pat::Path(path_pat) => {
3151 self.rename_path(&mut path_pat.path);
3152 }
3153 _ => {}
3154 }
3155
3156 syn::visit_mut::visit_pat_mut(self, pat);
3158 }
3159
3160 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
3162 match expr {
3163 syn::Expr::Path(expr_path) => {
3164 self.rename_path(&mut expr_path.path);
3165 }
3166 syn::Expr::Call(call) => {
3167 if let syn::Expr::Path(path) = &mut *call.func {
3168 self.rename_path(&mut path.path);
3169 }
3170 }
3171 syn::Expr::Struct(struct_expr) => {
3172 self.rename_path(&mut struct_expr.path);
3173 }
3174 _ => {}
3175 }
3176
3177 syn::visit_mut::visit_expr_mut(self, expr);
3179 }
3180}
3181
3182struct EnumVariantReplacementCollector {
3184 enum_name: String,
3185 old_variant: String,
3186 new_variant: String,
3187 path_resolver: Option<PathResolver>,
3188 replacements: Vec<crate::surgical::Replacement>,
3189}
3190
3191impl EnumVariantReplacementCollector {
3192 fn collect_path_replacement(&mut self, path: &syn::Path) {
3194 let segments: Vec<_> = path.segments.iter().collect();
3195 let len = segments.len();
3196
3197 if len >= 2 {
3198 let potential_variant = &segments[len - 1];
3199 let potential_enum = &segments[len - 2];
3200
3201 if potential_enum.ident == self.enum_name
3202 && potential_variant.ident == self.old_variant
3203 {
3204 let should_rename = if let Some(resolver) = &self.path_resolver {
3208 let enum_path = syn::Path {
3209 leading_colon: path.leading_colon,
3210 segments: path.segments.iter()
3211 .take(len - 1)
3212 .cloned()
3213 .collect(),
3214 };
3215 resolver.matches_target(&enum_path)
3216 } else {
3217 len == 2
3219 };
3220
3221 if should_rename {
3222 let span = potential_variant.ident.span();
3223 let start = span.start();
3224 let end = span.end();
3225
3226 self.replacements.push(crate::surgical::Replacement::new(
3227 start,
3228 end,
3229 self.new_variant.clone(),
3230 ));
3231 }
3232 }
3233 } else if len == 1 && self.path_resolver.is_none() {
3234 if segments[0].ident == self.old_variant {
3236 let span = segments[0].ident.span();
3237 let start = span.start();
3238 let end = span.end();
3239
3240 self.replacements.push(crate::surgical::Replacement::new(
3241 start,
3242 end,
3243 self.new_variant.clone(),
3244 ));
3245 }
3246 }
3247 }
3248}
3249
3250impl<'ast> syn::visit::Visit<'ast> for EnumVariantReplacementCollector {
3251 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
3252 if node.ident == self.enum_name {
3253 for variant in &node.variants {
3254 if variant.ident == self.old_variant {
3255 let span = variant.ident.span();
3256 let start = span.start();
3257 let end = span.end();
3258
3259 self.replacements.push(crate::surgical::Replacement::new(
3260 start,
3261 end,
3262 self.new_variant.clone(),
3263 ));
3264 }
3265 }
3266 }
3267 syn::visit::visit_item_enum(self, node);
3268 }
3269
3270 fn visit_pat(&mut self, pat: &'ast syn::Pat) {
3271 match pat {
3272 syn::Pat::TupleStruct(tuple_struct) => {
3273 self.collect_path_replacement(&tuple_struct.path);
3274 }
3275 syn::Pat::Struct(struct_pat) => {
3276 self.collect_path_replacement(&struct_pat.path);
3277 }
3278 syn::Pat::Path(path_pat) => {
3279 self.collect_path_replacement(&path_pat.path);
3280 }
3281 _ => {}
3282 }
3283 syn::visit::visit_pat(self, pat);
3284 }
3285
3286 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
3287 match expr {
3288 syn::Expr::Path(expr_path) => {
3289 self.collect_path_replacement(&expr_path.path);
3290 }
3291 syn::Expr::Call(call) => {
3292 if let syn::Expr::Path(path) = &*call.func {
3293 self.collect_path_replacement(&path.path);
3294 }
3295 }
3296 syn::Expr::Struct(struct_expr) => {
3297 self.collect_path_replacement(&struct_expr.path);
3298 }
3299 _ => {}
3300 }
3301 syn::visit::visit_expr(self, expr);
3302 }
3303}
3304
3305struct FunctionRenamer {
3307 old_name: String,
3308 new_name: String,
3309 path_resolver: Option<PathResolver>,
3310 modified: bool,
3311}
3312
3313impl FunctionRenamer {
3314 fn rename_ident(&mut self, ident: &mut syn::Ident) {
3316 if ident == &self.old_name {
3317 *ident = syn::Ident::new(&self.new_name, ident.span());
3318 self.modified = true;
3319 }
3320 }
3321
3322 fn matches_target_function(&self, path: &syn::Path) -> bool {
3324 if let Some(resolver) = &self.path_resolver {
3325 resolver.matches_target(path)
3326 } else {
3327 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
3329 }
3330 }
3331}
3332
3333impl VisitMut for FunctionRenamer {
3334 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
3335 self.rename_ident(&mut node.sig.ident);
3337 syn::visit_mut::visit_item_fn_mut(self, node);
3338 }
3339
3340 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
3341 match expr {
3342 syn::Expr::Call(call) => {
3343 if let syn::Expr::Path(expr_path) = &mut *call.func {
3345 if self.matches_target_function(&expr_path.path) {
3346 if let Some(last_seg) = expr_path.path.segments.last_mut() {
3347 self.rename_ident(&mut last_seg.ident);
3348 }
3349 }
3350 }
3351 }
3352 syn::Expr::Path(expr_path) => {
3353 if self.matches_target_function(&expr_path.path) {
3355 if let Some(last_seg) = expr_path.path.segments.last_mut() {
3356 self.rename_ident(&mut last_seg.ident);
3357 }
3358 }
3359 }
3360 _ => {}
3361 }
3362 syn::visit_mut::visit_expr_mut(self, expr);
3363 }
3364}
3365
3366struct FunctionReplacementCollector {
3368 old_name: String,
3369 new_name: String,
3370 path_resolver: Option<PathResolver>,
3371 replacements: Vec<crate::surgical::Replacement>,
3372}
3373
3374impl FunctionReplacementCollector {
3375 fn collect_replacement(&mut self, ident: &syn::Ident) {
3377 if ident == &self.old_name {
3378 let span = ident.span();
3379 let start = span.start();
3380 let end = span.end();
3381
3382 self.replacements.push(crate::surgical::Replacement::new(
3383 start,
3384 end,
3385 self.new_name.clone(),
3386 ));
3387 }
3388 }
3389
3390 fn matches_target_function(&self, path: &syn::Path) -> bool {
3392 if let Some(resolver) = &self.path_resolver {
3393 resolver.matches_target(path)
3394 } else {
3395 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
3397 }
3398 }
3399}
3400
3401impl<'ast> syn::visit::Visit<'ast> for FunctionReplacementCollector {
3402 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3403 self.collect_replacement(&node.sig.ident);
3405 syn::visit::visit_item_fn(self, node);
3406 }
3407
3408 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
3409 match expr {
3410 syn::Expr::Call(call) => {
3411 if let syn::Expr::Path(expr_path) = &*call.func {
3413 if self.matches_target_function(&expr_path.path) {
3414 if let Some(last_seg) = expr_path.path.segments.last() {
3415 self.collect_replacement(&last_seg.ident);
3416 }
3417 }
3418 }
3419 for arg in &call.args {
3421 syn::visit::visit_expr(self, arg);
3422 }
3423 return;
3425 }
3426 syn::Expr::Path(expr_path) => {
3427 if self.matches_target_function(&expr_path.path) {
3429 if let Some(last_seg) = expr_path.path.segments.last() {
3430 self.collect_replacement(&last_seg.ident);
3431 }
3432 }
3433 }
3434 _ => {}
3435 }
3436 syn::visit::visit_expr(self, expr);
3437 }
3438}
3439
3440fn generate_doc_comment(text: &str, style: &DocCommentStyle) -> String {
3446 match style {
3447 DocCommentStyle::Line => {
3448 text.lines()
3450 .map(|line| {
3451 if line.trim().is_empty() {
3452 "///".to_string()
3453 } else {
3454 format!("/// {}", line)
3455 }
3456 })
3457 .collect::<Vec<_>>()
3458 .join("\n")
3459 }
3460 DocCommentStyle::Block => {
3461 if text.contains('\n') {
3463 let lines = text.lines()
3465 .map(|line| format!(" * {}", line))
3466 .collect::<Vec<_>>()
3467 .join("\n");
3468 format!("/**\n{}\n */", lines)
3469 } else {
3470 format!("/** {} */", text)
3472 }
3473 }
3474 }
3475}
3476
3477struct TargetFinder {
3479 target_type: String,
3480 target_name: String,
3481 found_position: Option<(usize, String)>, }
3483
3484impl TargetFinder {
3485 fn new(target_type: String, target_name: String) -> Self {
3486 Self {
3487 target_type,
3488 target_name,
3489 found_position: None,
3490 }
3491 }
3492}
3493
3494impl<'ast> syn::visit::Visit<'ast> for TargetFinder {
3495 fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
3496 if self.target_type == "struct" && node.ident.to_string() == self.target_name {
3497 let line = node.struct_token.span.start().line;
3500 self.found_position = Some((line, String::new()));
3501 }
3502 syn::visit::visit_item_struct(self, node);
3503 }
3504
3505 fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
3506 if self.target_type == "enum" && node.ident.to_string() == self.target_name {
3507 let line = node.enum_token.span.start().line;
3509 self.found_position = Some((line, String::new()));
3510 }
3511 syn::visit::visit_item_enum(self, node);
3512 }
3513
3514 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3515 if self.target_type == "function" && node.sig.ident.to_string() == self.target_name {
3516 let line = node.sig.fn_token.span.start().line;
3518 self.found_position = Some((line, String::new()));
3519 }
3520 syn::visit::visit_item_fn(self, node);
3521 }
3522}
3523
3524impl RustEditor {
3525 pub fn add_doc_comment_surgical(
3527 &mut self,
3528 target_type: &str,
3529 target_name: &str,
3530 doc_text: &str,
3531 style: &DocCommentStyle,
3532 ) -> Result<ModificationResult> {
3533 use syn::visit::Visit;
3534
3535 let mut finder = TargetFinder::new(
3537 target_type.to_string(),
3538 target_name.to_string(),
3539 );
3540 finder.visit_file(&self.syntax_tree);
3541
3542 if let Some((line_num, _indent)) = finder.found_position {
3543 let line_idx = line_num.saturating_sub(1);
3545
3546 let comment = generate_doc_comment(doc_text, style);
3548
3549 let lines: Vec<&str> = self.content.lines().collect();
3551 if line_idx >= lines.len() {
3552 anyhow::bail!("Target not found at line {}", line_num);
3553 }
3554
3555 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
3557
3558 let indent = target_line
3560 .chars()
3561 .take_while(|c| c.is_whitespace())
3562 .collect::<String>();
3563
3564 let mut new_lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
3566
3567 let comment_lines: Vec<String> = comment
3569 .lines()
3570 .map(|line| format!("{}{}", indent, line))
3571 .collect();
3572
3573 for (i, comment_line) in comment_lines.iter().rev().enumerate() {
3575 new_lines.insert(line_idx, comment_line.clone());
3576 }
3577
3578 self.content = new_lines.join("\n");
3580
3581 self.syntax_tree = syn::parse_str(&self.content)
3583 .context("Failed to re-parse after adding comment")?;
3584
3585 Ok(ModificationResult {
3586 changed: true,
3587 modified_nodes: vec![BackupNode {
3588 node_type: target_type.to_string(),
3589 identifier: target_name.to_string(),
3590 original_content: target_line,
3591 location: NodeLocation {
3592 line: line_num,
3593 column: 1,
3594 end_line: line_num,
3595 end_column: target_line_len,
3596 },
3597 }],
3598 })
3599 } else {
3600 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
3601 }
3602 }
3603
3604 pub fn update_doc_comment_surgical(
3606 &mut self,
3607 target_type: &str,
3608 target_name: &str,
3609 doc_text: &str,
3610 style: &DocCommentStyle,
3611 ) -> Result<ModificationResult> {
3612 use syn::visit::Visit;
3613
3614 let mut finder = TargetFinder::new(
3616 target_type.to_string(),
3617 target_name.to_string(),
3618 );
3619 finder.visit_file(&self.syntax_tree);
3620
3621 if let Some((line_num, _indent)) = finder.found_position {
3622 let line_idx = line_num.saturating_sub(1);
3624
3625 let lines: Vec<&str> = self.content.lines().collect();
3627 if line_idx >= lines.len() {
3628 anyhow::bail!("Target not found at line {}", line_num);
3629 }
3630
3631 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
3633
3634 let indent = target_line
3636 .chars()
3637 .take_while(|c| c.is_whitespace())
3638 .collect::<String>();
3639
3640 let mut doc_comment_start = line_idx;
3642 while doc_comment_start > 0 {
3643 let prev_line = lines[doc_comment_start - 1].trim();
3644 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
3645 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
3646 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
3647 prev_line == "*/" {
3648 doc_comment_start -= 1;
3649 } else {
3650 break;
3651 }
3652 }
3653
3654 let mut new_lines: Vec<String> = Vec::new();
3656
3657 for i in 0..doc_comment_start {
3659 new_lines.push(lines[i].to_string());
3660 }
3661
3662 let comment = generate_doc_comment(doc_text, style);
3664 let comment_lines: Vec<String> = comment
3665 .lines()
3666 .map(|line| format!("{}{}", indent, line))
3667 .collect();
3668
3669 for comment_line in comment_lines {
3670 new_lines.push(comment_line);
3671 }
3672
3673 for i in line_idx..lines.len() {
3675 new_lines.push(lines[i].to_string());
3676 }
3677
3678 self.content = new_lines.join("\n");
3680
3681 self.syntax_tree = syn::parse_str(&self.content)
3683 .context("Failed to re-parse after updating comment")?;
3684
3685 Ok(ModificationResult {
3686 changed: true,
3687 modified_nodes: vec![BackupNode {
3688 node_type: target_type.to_string(),
3689 identifier: target_name.to_string(),
3690 original_content: target_line,
3691 location: NodeLocation {
3692 line: line_num,
3693 column: 1,
3694 end_line: line_num,
3695 end_column: target_line_len,
3696 },
3697 }],
3698 })
3699 } else {
3700 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
3701 }
3702 }
3703
3704 pub fn remove_doc_comment_surgical(
3706 &mut self,
3707 target_type: &str,
3708 target_name: &str,
3709 ) -> Result<ModificationResult> {
3710 use syn::visit::Visit;
3711
3712 let mut finder = TargetFinder::new(
3714 target_type.to_string(),
3715 target_name.to_string(),
3716 );
3717 finder.visit_file(&self.syntax_tree);
3718
3719 if let Some((line_num, _indent)) = finder.found_position {
3720 let line_idx = line_num.saturating_sub(1);
3722
3723 let lines: Vec<&str> = self.content.lines().collect();
3725 if line_idx >= lines.len() {
3726 anyhow::bail!("Target not found at line {}", line_num);
3727 }
3728
3729 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
3731
3732 let mut doc_comment_start = line_idx;
3734 while doc_comment_start > 0 {
3735 let prev_line = lines[doc_comment_start - 1].trim();
3736 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
3737 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
3738 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
3739 prev_line == "*/" {
3740 doc_comment_start -= 1;
3741 } else {
3742 break;
3743 }
3744 }
3745
3746 let mut new_lines: Vec<String> = Vec::new();
3748
3749 for i in 0..doc_comment_start {
3751 new_lines.push(lines[i].to_string());
3752 }
3753
3754 for i in line_idx..lines.len() {
3758 new_lines.push(lines[i].to_string());
3759 }
3760
3761 self.content = new_lines.join("\n");
3763
3764 self.syntax_tree = syn::parse_str(&self.content)
3766 .context("Failed to re-parse after removing comment")?;
3767
3768 Ok(ModificationResult {
3769 changed: true,
3770 modified_nodes: vec![BackupNode {
3771 node_type: target_type.to_string(),
3772 identifier: target_name.to_string(),
3773 original_content: target_line,
3774 location: NodeLocation {
3775 line: line_num,
3776 column: 1,
3777 end_line: line_num,
3778 end_column: target_line_len,
3779 },
3780 }],
3781 })
3782 } else {
3783 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
3784 }
3785 }
3786}