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 fn find_similar_fields(target: &str, available: &[String]) -> Vec<String> {
71 use strsim::levenshtein;
72
73 let mut scored: Vec<_> = available.iter()
74 .map(|field| (field, levenshtein(target, field)))
75 .filter(|(_, distance)| *distance <= 3) .collect();
77
78 scored.sort_by_key(|(_, distance)| *distance);
79 scored.into_iter()
80 .take(3) .map(|(field, _)| field.to_string())
82 .collect()
83 }
84
85 pub fn apply_operation(&mut self, op: &Operation) -> Result<ModificationResult> {
86 match op {
87 Operation::AddStructField(op) => self.add_struct_field(op),
88 Operation::UpdateStructField(op) => self.update_struct_field(op),
89 Operation::RemoveStructField(op) => self.remove_struct_field(op),
90 Operation::AddStructLiteralField(op) => self.add_struct_literal_field(op),
91 Operation::AddEnumVariant(op) => self.add_enum_variant(op),
92 Operation::UpdateEnumVariant(op) => self.update_enum_variant(op),
93 Operation::RemoveEnumVariant(op) => self.remove_enum_variant(op),
94 Operation::AddMatchArm(op) => self.add_match_arm(op),
95 Operation::UpdateMatchArm(op) => self.update_match_arm(op),
96 Operation::RemoveMatchArm(op) => self.remove_match_arm(op),
97 Operation::AddImplMethod(op) => self.add_impl_method(op),
98 Operation::AddUseStatement(op) => self.add_use_statement(op),
99 Operation::AddDerive(op) => self.add_derive(op),
100 Operation::Transform(op) => self.transform(op),
101 Operation::RenameEnumVariant(op) => self.rename_enum_variant(op),
102 Operation::RenameFunction(op) => self.rename_function(op),
103 Operation::AddDocComment(op) => self.add_doc_comment_surgical(
104 &op.target_type,
105 &op.name,
106 &op.doc_comment,
107 &op.style,
108 ),
109 Operation::UpdateDocComment(op) => self.update_doc_comment_surgical(
110 &op.target_type,
111 &op.name,
112 &op.doc_comment,
113 &DocCommentStyle::Line, ),
115 Operation::RemoveDocComment(op) => self.remove_doc_comment_surgical(
116 &op.target_type,
117 &op.name,
118 ),
119 }
120 }
121
122 pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
123 let mut modified_nodes = Vec::new();
124
125 let is_enum_variant = op.struct_name.contains("::");
127
128 let has_type = op.field_def.contains(':');
131 let is_literal_only = op.literal_default.is_some() && !has_type;
132
133 if is_enum_variant || is_literal_only {
137 let final_field_def = if let Some(literal_default) = &op.literal_default {
144 let field_name = op.field_def.split(':')
146 .next()
147 .map(|s| s.trim().to_string())
148 .context("Failed to extract field name")?;
149 format!("{}: {}", field_name, literal_default)
150 } else if op.field_def.contains(':') {
151 op.field_def.clone()
153 } else {
154 anyhow::bail!(
155 "For enum variant literals, field definition must include a value.\n\
156 Either use: --field \"layer: None\" or --field \"layer\" --literal-default \"None\""
157 );
158 };
159
160 let literal_op = AddStructLiteralFieldOp {
162 struct_name: op.struct_name.clone(),
163 field_def: final_field_def,
164 position: op.position.clone(),
165 struct_path: None,
166 };
167
168 let literal_result = self.add_struct_literal_field(&literal_op)
170 .context("Failed to update struct literals")?;
171
172 return Ok(literal_result);
173 }
174
175 let item_struct = self.syntax_tree.items.iter()
177 .find_map(|item| {
178 if let Item::Struct(s) = item {
179 if s.ident == op.struct_name {
180 return Some(s.clone());
181 }
182 }
183 None
184 })
185 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
186
187 if let Some(ref where_filter) = op.where_filter {
189 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
190 return Ok(ModificationResult {
192 changed: false,
193 modified_nodes: vec![],
194 unmatched_qualified_paths: None,
195 });
196 }
197 }
198
199 if op.literal_default.is_none() {
201 let backup_node = BackupNode {
203 node_type: "struct".to_string(),
204 identifier: op.struct_name.clone(),
205 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
206 location: self.span_to_location(item_struct.span()),
207 };
208
209 let modified = self.insert_struct_field(&item_struct, op)
211 .context("Failed to add field to struct definition")?;
212
213 if !modified {
214 return Ok(ModificationResult {
215 changed: false,
216 modified_nodes: vec![],
217 unmatched_qualified_paths: None,
218 });
219 }
220
221 return Ok(ModificationResult {
222 changed: true,
223 modified_nodes: vec![backup_node],
224 unmatched_qualified_paths: None,
225 });
226 }
227
228 let literal_default = op.literal_default.as_ref().unwrap();
232
233 let has_type = op.field_def.contains(':');
236
237 let mut def_modified = false;
238 if has_type {
239 let backup_node = BackupNode {
241 node_type: "struct".to_string(),
242 identifier: op.struct_name.clone(),
243 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
244 location: self.span_to_location(item_struct.span()),
245 };
246
247 def_modified = self.insert_struct_field(&item_struct, op)
249 .context("Failed to add field to struct definition")?;
250
251 if def_modified {
252 modified_nodes.push(backup_node);
253 self.syntax_tree = syn::parse_str(&self.content)
255 .context("Failed to re-parse content after adding struct field")?;
256 self.line_offsets = Self::compute_line_offsets(&self.content);
257 }
258 }
259
260 let field_name = op.field_def.split(':')
263 .next()
264 .map(|s| s.trim().to_string())
265 .context("Failed to extract field name from field definition")?;
266
267 let literal_op = AddStructLiteralFieldOp {
269 struct_name: op.struct_name.clone(),
270 field_def: format!("{}: {}", field_name, literal_default),
271 position: op.position.clone(),
272 struct_path: None, };
274
275 let literal_result = self.add_struct_literal_field(&literal_op)
277 .context("Failed to update struct literals")?;
278 modified_nodes.extend(literal_result.modified_nodes);
279
280 Ok(ModificationResult {
281 changed: true,
282 modified_nodes,
283 unmatched_qualified_paths: None,
284 })
285 }
286
287 fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
288 if let Fields::Named(ref fields) = item_struct.fields {
289 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
291 let dummy: ItemStruct = parse_str(&field_code)
292 .context("Failed to parse field definition")?;
293
294 let new_field = if let Fields::Named(ref nf) = dummy.fields {
295 nf.named.first()
296 .context("No field found in definition")?
297 .clone()
298 } else {
299 anyhow::bail!("Expected named field");
300 };
301
302 let new_field_name = new_field.ident.as_ref()
304 .map(|i| i.to_string())
305 .context("Field must have a name")?;
306
307 if fields.named.iter().any(|f| {
308 f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
309 }) {
310 return Ok(false);
312 }
313
314 let insert_pos = match &op.position {
316 InsertPosition::First => {
317 if let Some(first_field) = fields.named.first() {
318 self.span_to_byte_offset(first_field.span().start())
319 } else {
320 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
322 brace_pos + 1
323 }
324 }
325 InsertPosition::Last => {
326 if let Some(last_field) = fields.named.last() {
327 let end = self.span_to_byte_offset(last_field.span().end());
328 self.find_after_field_end(end)
330 } else {
331 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
333 brace_pos + 1
334 }
335 }
336 InsertPosition::After(name) => {
337 let field = fields.named.iter()
338 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
339 .with_context(|| format!("Field '{}' not found", name))?;
340 let end = self.span_to_byte_offset(field.span().end());
341 self.find_after_field_end(end)
342 }
343 InsertPosition::Before(name) => {
344 let field = fields.named.iter()
345 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
346 .with_context(|| format!("Field '{}' not found", name))?;
347 self.span_to_byte_offset(field.span().start())
348 }
349 };
350
351 let indent = self.get_indentation(insert_pos);
353 let field_str = Self::format_field(&new_field);
354 let insert_text = if matches!(op.position, InsertPosition::First) {
355 format!("\n{}{},", indent, field_str)
356 } else {
357 format!("\n{}{},", indent, field_str)
358 };
359
360 self.content.insert_str(insert_pos, &insert_text);
361 return Ok(true);
362 }
363
364 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
365 }
366
367 pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
368 let is_enum_variant = op.struct_name.contains("::");
370
371 if is_enum_variant {
374 anyhow::bail!(
375 "Cannot update field in enum variant definition '{}'.\n\
376 To update fields in enum variant struct literals, use the transform command:\n\
377 rs-hack transform --node-type struct-literal --name {} --action replace --with <new_pattern> --paths ... --apply",
378 op.struct_name, op.struct_name
379 );
380 }
381
382 let item_struct = self.syntax_tree.items.iter()
384 .find_map(|item| {
385 if let Item::Struct(s) = item {
386 if s.ident == op.struct_name {
387 return Some(s.clone());
388 }
389 }
390 None
391 })
392 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
393
394 if let Some(ref where_filter) = op.where_filter {
396 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
397 return Ok(ModificationResult {
399 changed: false,
400 modified_nodes: vec![],
401 unmatched_qualified_paths: None,
402 });
403 }
404 }
405
406 let backup_node = BackupNode {
408 node_type: "struct".to_string(),
409 identifier: op.struct_name.clone(),
410 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
411 location: self.span_to_location(item_struct.span()),
412 };
413
414 let modified = self.replace_struct_field(&item_struct, op)?;
415
416 Ok(ModificationResult {
417 changed: modified,
418 modified_nodes: if modified { vec![backup_node] } else { vec![] },
419 unmatched_qualified_paths: None,
420 })
421 }
422
423 fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
424 if let Fields::Named(ref fields) = item_struct.fields {
425 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
427 let dummy: ItemStruct = parse_str(&field_code)
428 .context("Failed to parse field definition")?;
429
430 let new_field = if let Fields::Named(ref nf) = dummy.fields {
431 nf.named.first()
432 .context("No field found in definition")?
433 .clone()
434 } else {
435 anyhow::bail!("Expected named field");
436 };
437
438 let field_name = new_field.ident.as_ref()
440 .map(|i| i.to_string())
441 .context("Field must have a name")?;
442
443 let existing_field = fields.named.iter()
445 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
446 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
447
448 let start = self.span_to_byte_offset(existing_field.span().start());
450 let end = self.span_to_byte_offset(existing_field.span().end());
451
452 let new_field_str = Self::format_field(&new_field);
454
455 self.content.replace_range(start..end, &new_field_str);
457
458 return Ok(true);
459 }
460
461 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
462 }
463
464 pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
465 let mut modified_nodes = Vec::new();
466 let mut changed = false;
467
468 let is_enum_variant = op.struct_name.contains("::");
470
471 let effective_literal_only = op.literal_only || is_enum_variant;
474
475 if !effective_literal_only {
477 let item_struct = self.syntax_tree.items.iter()
479 .find_map(|item| {
480 if let Item::Struct(s) = item {
481 if s.ident == op.struct_name {
482 return Some(s.clone());
483 }
484 }
485 None
486 })
487 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
488
489 if let Some(ref where_filter) = op.where_filter {
491 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
492 return Ok(ModificationResult {
494 changed: false,
495 modified_nodes: vec![],
496 unmatched_qualified_paths: None,
497 });
498 }
499 }
500
501 let backup_node = BackupNode {
503 node_type: "struct".to_string(),
504 identifier: op.struct_name.clone(),
505 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
506 location: self.span_to_location(item_struct.span()),
507 };
508
509 if let Fields::Named(ref fields) = item_struct.fields {
510 let field_to_remove = fields.named.iter()
512 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()));
513
514 if field_to_remove.is_none() {
516 let field_names: Vec<String> = fields.named.iter()
517 .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
518 .collect();
519
520 let suggestions = Self::find_similar_fields(&op.field_name, &field_names);
521
522 if suggestions.is_empty() {
523 return Err(anyhow::anyhow!(
524 "Field '{}' not found in struct '{}'\n\nAvailable fields: {}",
525 op.field_name,
526 op.struct_name,
527 field_names.join(", ")
528 ));
529 } else {
530 return Err(anyhow::anyhow!(
531 "Field '{}' not found in struct '{}'\n\nDid you mean one of these?\n - {}\n\nAll available fields: {}",
532 op.field_name,
533 op.struct_name,
534 suggestions.join("\n - "),
535 field_names.join(", ")
536 ));
537 }
538 }
539
540 let field_to_remove = field_to_remove.unwrap();
541
542 let start = self.span_to_byte_offset(field_to_remove.span().start());
544 let mut end = self.span_to_byte_offset(field_to_remove.span().end());
545
546 while end < self.content.len() {
548 match self.content.as_bytes()[end] as char {
549 ',' => {
550 end += 1;
551 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
553 end += 1;
554 }
555 break;
556 }
557 ' ' | '\t' => end += 1,
558 '\n' => {
559 end += 1;
560 break;
561 }
562 _ => break,
563 }
564 }
565
566 let mut line_start = start;
568 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
569 line_start -= 1;
570 }
571
572 let before_field = &self.content[line_start..start];
574 if before_field.trim().is_empty() {
575 self.content.replace_range(line_start..end, "");
577 } else {
578 self.content.replace_range(start..end, "");
580 }
581
582 modified_nodes.push(backup_node);
583 changed = true;
584
585 self.syntax_tree = syn::parse_str(&self.content)?;
587 } else {
588 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
589 }
590 }
591
592 let literal_backups = self.collect_struct_literal_backups(&op.struct_name, None);
595
596 use syn::visit::Visit;
598
599 struct FieldDeletionFinder<'a> {
600 struct_name: String,
601 field_name: String,
602 deletion_ranges: Vec<(usize, usize)>, unmatched_paths: std::collections::HashMap<String, usize>, editor: &'a RustEditor,
605 }
606
607 impl<'ast, 'a> Visit<'ast> for FieldDeletionFinder<'a> {
608 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
609 let matches = if self.struct_name.contains("::") {
611 if self.struct_name.starts_with("*::") {
612 let target_name = &self.struct_name[3..];
613 node.path.segments.last()
614 .map(|seg| seg.ident.to_string() == target_name)
615 .unwrap_or(false)
616 } else {
617 let path_str = node.path.segments.iter()
618 .map(|seg| seg.ident.to_string())
619 .collect::<Vec<_>>()
620 .join("::");
621 path_str == self.struct_name
622 }
623 } else {
624 let matches = node.path.segments.len() == 1
626 && node.path.segments.last()
627 .map(|seg| seg.ident.to_string())
628 .as_ref() == Some(&self.struct_name);
629
630 if !matches && node.path.segments.len() > 1 {
632 if let Some(last_seg) = node.path.segments.last() {
633 if last_seg.ident.to_string() == self.struct_name {
634 let qualified_path = node.path.segments.iter()
635 .map(|seg| seg.ident.to_string())
636 .collect::<Vec<_>>()
637 .join("::");
638 *self.unmatched_paths.entry(qualified_path).or_insert(0) += 1;
639 }
640 }
641 }
642 matches
643 };
644
645 if matches {
646 let field_idx = node.fields.iter().position(|fv| {
648 if let syn::Member::Named(ident) = &fv.member {
649 ident.to_string() == self.field_name
650 } else {
651 false
652 }
653 });
654
655 if let Some(idx) = field_idx {
656 let field = &node.fields[idx];
657 let start = self.editor.span_to_byte_offset(field.span().start());
658 let mut end = self.editor.span_to_byte_offset(field.span().end());
659
660 let content_bytes = self.editor.content.as_bytes();
663 while end < content_bytes.len() {
664 match content_bytes[end] {
665 b',' => {
666 end += 1;
667 if end < content_bytes.len() && content_bytes[end] == b'\n' {
669 end += 1;
670 }
671 break;
672 }
673 b' ' | b'\t' => {
674 end += 1;
675 }
676 _ => break,
677 }
678 }
679
680 let mut line_start = start;
682 while line_start > 0 {
683 let ch = content_bytes[line_start - 1];
684 if ch == b'\n' {
685 break;
686 } else if ch == b' ' || ch == b'\t' {
687 line_start -= 1;
688 } else {
689 break;
690 }
691 }
692
693 self.deletion_ranges.push((line_start, end));
694 }
695 }
696
697 syn::visit::visit_expr_struct(self, node);
698 }
699 }
700
701 let mut finder = FieldDeletionFinder {
702 struct_name: op.struct_name.clone(),
703 field_name: op.field_name.clone(),
704 deletion_ranges: Vec::new(),
705 unmatched_paths: std::collections::HashMap::new(),
706 editor: self,
707 };
708
709 finder.visit_file(&self.syntax_tree);
710
711 let unmatched_hint = if !op.struct_name.contains("::") && !finder.unmatched_paths.is_empty() {
713 Some(finder.unmatched_paths)
714 } else {
715 None
716 };
717
718 if !finder.deletion_ranges.is_empty() {
719 let mut ranges = finder.deletion_ranges;
721 ranges.sort_by_key(|(start, _)| std::cmp::Reverse(*start));
722
723 for (start, end) in ranges {
725 self.content.drain(start..end);
726 }
727
728 self.syntax_tree = syn::parse_str(&self.content)
730 .context("Failed to re-parse after removing struct literal fields")?;
731 self.line_offsets = Self::compute_line_offsets(&self.content);
732
733 modified_nodes.extend(literal_backups);
734 changed = true;
735 }
736
737 Ok(ModificationResult {
738 changed,
739 modified_nodes,
740 unmatched_qualified_paths: unmatched_hint,
741 })
742 }
743
744 pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
745 let field_name = op.field_def.split(':')
747 .next()
748 .map(|s| s.trim().to_string())
749 .context("Field definition must contain ':'")?;
750
751 let path_resolver = if let Some(struct_path) = &op.struct_path {
753 let mut resolver = PathResolver::new(struct_path)
754 .ok_or_else(|| anyhow::anyhow!("Invalid struct path: {}", struct_path))?;
755
756 resolver.scan_file(&self.syntax_tree);
758 Some(resolver)
759 } else {
760 None
761 };
762
763 let backup_nodes = self.collect_struct_literal_backups(&op.struct_name, path_resolver.as_ref());
765
766 use syn::visit::Visit;
768
769 struct LiteralFieldInserter<'a> {
770 struct_name: String,
771 field_name: String,
772 path_resolver: Option<&'a PathResolver>,
773 insertion_points: Vec<(usize, usize)>, unmatched_paths: std::collections::HashMap<String, usize>, editor: &'a RustEditor,
776 }
777
778 impl<'ast, 'a> Visit<'ast> for LiteralFieldInserter<'a> {
779 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
780 use syn::parse::Parser;
782
783 if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
784 .parse2(node.mac.tokens.clone())
785 {
786 for expr in exprs.iter() {
787 syn::visit::visit_expr(self, expr);
788 }
789 }
790
791 syn::visit::visit_expr_macro(self, node);
792 }
793
794 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
795 let is_match = if let Some(resolver) = &self.path_resolver {
797 resolver.matches_target(&node.path)
798 } else {
799 if self.struct_name.contains("::") {
801 if self.struct_name.starts_with("*::") {
802 let target_name = &self.struct_name[3..];
803 node.path.segments.last()
804 .map(|seg| seg.ident.to_string() == target_name)
805 .unwrap_or(false)
806 } else {
807 let path_str = node.path.segments.iter()
808 .map(|seg| seg.ident.to_string())
809 .collect::<Vec<_>>()
810 .join("::");
811 path_str == self.struct_name
812 }
813 } else {
814 let matches = node.path.segments.len() == 1
816 && node.path.segments.last()
817 .map(|seg| seg.ident.to_string())
818 .as_ref() == Some(&self.struct_name);
819
820 if !matches && node.path.segments.len() > 1 {
822 if let Some(last_seg) = node.path.segments.last() {
823 if last_seg.ident.to_string() == self.struct_name {
824 let qualified_path = node.path.segments.iter()
825 .map(|seg| seg.ident.to_string())
826 .collect::<Vec<_>>()
827 .join("::");
828 *self.unmatched_paths.entry(qualified_path).or_insert(0) += 1;
829 }
830 }
831 }
832 matches
833 }
834 };
835
836 if is_match {
837 let field_exists = node.fields.iter().any(|fv| {
839 fv.member.to_token_stream().to_string() == self.field_name
840 });
841
842 if !field_exists {
843 let insert_offset = if let Some(last_field) = node.fields.last() {
845 self.editor.span_to_byte_offset(last_field.span().end())
847 } else {
848 let brace_pos = self.editor.span_to_byte_offset(node.brace_token.span.join().start());
850 brace_pos + 1 };
852
853 let indent = if let Some(last_field) = node.fields.last() {
855 let line_start = self.editor.span_to_byte_offset(last_field.span().start());
856 self.editor.get_indentation(line_start).len()
857 } else {
858 let struct_start = self.editor.span_to_byte_offset(node.span().start());
860 self.editor.get_indentation(struct_start).len() + 4
861 };
862
863 self.insertion_points.push((insert_offset, indent));
864 }
865 }
866
867 syn::visit::visit_expr_struct(self, node);
868 }
869 }
870
871 let mut inserter = LiteralFieldInserter {
872 struct_name: op.struct_name.clone(),
873 field_name: field_name.clone(),
874 path_resolver: path_resolver.as_ref(),
875 insertion_points: Vec::new(),
876 unmatched_paths: std::collections::HashMap::new(),
877 editor: self,
878 };
879
880 inserter.visit_file(&self.syntax_tree);
881
882 let unmatched_hint = if !op.struct_name.contains("::") && !inserter.unmatched_paths.is_empty() {
884 Some(inserter.unmatched_paths)
885 } else {
886 None
887 };
888
889 if inserter.insertion_points.is_empty() {
890 return Ok(ModificationResult {
891 changed: false,
892 modified_nodes: vec![],
893 unmatched_qualified_paths: unmatched_hint,
894 });
895 }
896
897 let mut points = inserter.insertion_points;
900 points.sort_by_key(|(offset, _)| std::cmp::Reverse(*offset));
901
902 for (insert_offset, indent_spaces) in points {
904 let indent = " ".repeat(indent_spaces);
905 let field_str = format!(",\n{}{}", indent, op.field_def);
906 self.content.insert_str(insert_offset, &field_str);
907 }
908
909 self.syntax_tree = syn::parse_str(&self.content)
911 .context("Failed to re-parse after adding struct literal fields")?;
912 self.line_offsets = Self::compute_line_offsets(&self.content);
913
914 Ok(ModificationResult {
915 changed: true,
916 modified_nodes: backup_nodes,
917 unmatched_qualified_paths: unmatched_hint,
918 })
919 }
920
921 fn collect_struct_literal_backups(&self, struct_name: &str, path_resolver: Option<&PathResolver>) -> Vec<BackupNode> {
923 use syn::visit::Visit;
924 use syn::spanned::Spanned;
925
926 struct LiteralCollector<'a> {
927 struct_name: String,
928 path_resolver: Option<&'a PathResolver>,
929 backups: Vec<BackupNode>,
930 counter: usize,
931 editor: &'a RustEditor,
932 }
933
934 impl<'ast, 'a> Visit<'ast> for LiteralCollector<'a> {
935 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
936 use syn::parse::Parser;
938
939 if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
940 .parse2(node.mac.tokens.clone())
941 {
942 for expr in exprs.iter() {
943 syn::visit::visit_expr(self, expr);
944 }
945 }
946
947 syn::visit::visit_expr_macro(self, node);
948 }
949
950 fn visit_expr(&mut self, node: &'ast Expr) {
951 if let Expr::Struct(expr_struct) = node {
952 let matches = if let Some(resolver) = self.path_resolver {
953 resolver.matches_target(&expr_struct.path)
955 } else {
956 if self.struct_name.contains("::") {
962 if self.struct_name.starts_with("*::") {
964 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
967 .map(|seg| seg.ident.to_string() == target_name)
968 .unwrap_or(false)
969 } else {
970 let path_str = expr_struct.path.segments.iter()
972 .map(|seg| seg.ident.to_string())
973 .collect::<Vec<_>>()
974 .join("::");
975 path_str == self.struct_name
976 }
977 } else {
978 expr_struct.path.segments.len() == 1
980 && expr_struct.path.segments.last()
981 .map(|seg| seg.ident.to_string() == self.struct_name)
982 .unwrap_or(false)
983 }
984 };
985
986 if matches {
987 let start = self.editor.span_to_byte_offset(expr_struct.span().start());
989 let end = self.editor.span_to_byte_offset(expr_struct.span().end());
990 let original_source = &self.editor.content[start..end];
991
992 self.backups.push(BackupNode {
993 node_type: "struct-literal".to_string(),
994 identifier: format!("{}#{}", self.struct_name, self.counter),
995 original_content: original_source.to_string(),
996 location: NodeLocation {
997 line: 0, column: 0,
999 end_line: 0,
1000 end_column: 0,
1001 },
1002 });
1003 self.counter += 1;
1004 }
1005 }
1006 syn::visit::visit_expr(self, node);
1007 }
1008 }
1009
1010 let mut collector = LiteralCollector {
1011 struct_name: struct_name.to_string(),
1012 path_resolver,
1013 backups: Vec::new(),
1014 counter: 0,
1015 editor: self,
1016 };
1017
1018 collector.visit_file(&self.syntax_tree);
1019 collector.backups
1020 }
1021
1022 pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
1023 let item_enum = self.syntax_tree.items.iter()
1025 .find_map(|item| {
1026 if let Item::Enum(e) = item {
1027 if e.ident == op.enum_name {
1028 return Some(e.clone());
1029 }
1030 }
1031 None
1032 })
1033 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1034
1035 if let Some(ref where_filter) = op.where_filter {
1037 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1038 return Ok(ModificationResult {
1040 changed: false,
1041 modified_nodes: vec![],
1042 unmatched_qualified_paths: None,
1043 });
1044 }
1045 }
1046
1047 let backup_node = BackupNode {
1049 node_type: "enum".to_string(),
1050 identifier: op.enum_name.clone(),
1051 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1052 location: self.span_to_location(item_enum.span()),
1053 };
1054
1055 let modified = self.insert_enum_variant(&item_enum, op)?;
1056
1057 Ok(ModificationResult {
1058 changed: modified,
1059 modified_nodes: if modified { vec![backup_node] } else { vec![] },
1060 unmatched_qualified_paths: None,
1061 })
1062 }
1063
1064 fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
1065 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
1067 let dummy: ItemEnum = parse_str(&variant_code)
1068 .context("Failed to parse variant definition")?;
1069
1070 let new_variant = dummy.variants.first()
1071 .context("No variant found in definition")?
1072 .clone();
1073
1074 let variant_name = new_variant.ident.to_string();
1076 if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
1077 return Ok(false);
1079 }
1080
1081 let insert_pos = match &op.position {
1083 InsertPosition::First => {
1084 if let Some(first_var) = item_enum.variants.first() {
1085 self.span_to_byte_offset(first_var.span().start())
1086 } else {
1087 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
1088 brace_pos + 1
1089 }
1090 }
1091 InsertPosition::Last => {
1092 if let Some(last_var) = item_enum.variants.last() {
1093 let end = self.span_to_byte_offset(last_var.span().end());
1094 self.find_after_field_end(end)
1095 } else {
1096 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
1097 brace_pos + 1
1098 }
1099 }
1100 InsertPosition::After(name) => {
1101 let variant = item_enum.variants.iter()
1102 .find(|v| v.ident.to_string() == *name)
1103 .with_context(|| format!("Variant '{}' not found", name))?;
1104 let end = self.span_to_byte_offset(variant.span().end());
1105 self.find_after_field_end(end)
1106 }
1107 InsertPosition::Before(name) => {
1108 let variant = item_enum.variants.iter()
1109 .find(|v| v.ident.to_string() == *name)
1110 .with_context(|| format!("Variant '{}' not found", name))?;
1111 self.span_to_byte_offset(variant.span().start())
1112 }
1113 };
1114
1115 let indent = self.get_indentation(insert_pos);
1116 let variant_str = new_variant.to_token_stream().to_string();
1117 let insert_text = format!("\n{}{},", indent, variant_str);
1118
1119 self.content.insert_str(insert_pos, &insert_text);
1120 Ok(true)
1121 }
1122
1123 fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
1124 let item_enum = self.syntax_tree.items.iter()
1126 .find_map(|item| {
1127 if let Item::Enum(e) = item {
1128 if e.ident == op.enum_name {
1129 return Some(e.clone());
1130 }
1131 }
1132 None
1133 })
1134 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1135
1136 if let Some(ref where_filter) = op.where_filter {
1138 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1139 return Ok(ModificationResult {
1141 changed: false,
1142 modified_nodes: vec![],
1143 unmatched_qualified_paths: None,
1144 });
1145 }
1146 }
1147
1148 let backup_node = BackupNode {
1150 node_type: "enum".to_string(),
1151 identifier: op.enum_name.clone(),
1152 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1153 location: self.span_to_location(item_enum.span()),
1154 };
1155
1156 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
1158 let dummy: ItemEnum = parse_str(&variant_code)
1159 .context("Failed to parse variant definition")?;
1160
1161 let new_variant = dummy.variants.first()
1162 .context("No variant found in definition")?
1163 .clone();
1164
1165 let variant_name = new_variant.ident.to_string();
1166
1167 let existing_variant = item_enum.variants.iter()
1169 .find(|v| v.ident.to_string() == variant_name)
1170 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
1171
1172 let start = self.span_to_byte_offset(existing_variant.span().start());
1174 let end = self.span_to_byte_offset(existing_variant.span().end());
1175
1176 let variant_str = new_variant.to_token_stream().to_string();
1178 self.content.replace_range(start..end, &variant_str);
1179
1180 Ok(ModificationResult {
1181 changed: true,
1182 modified_nodes: vec![backup_node],
1183 unmatched_qualified_paths: None,
1184 })
1185 }
1186
1187 pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
1188 let item_enum = self.syntax_tree.items.iter()
1190 .find_map(|item| {
1191 if let Item::Enum(e) = item {
1192 if e.ident == op.enum_name {
1193 return Some(e.clone());
1194 }
1195 }
1196 None
1197 })
1198 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1199
1200 if let Some(ref where_filter) = op.where_filter {
1202 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1203 return Ok(ModificationResult {
1205 changed: false,
1206 modified_nodes: vec![],
1207 unmatched_qualified_paths: None,
1208 });
1209 }
1210 }
1211
1212 let backup_node = BackupNode {
1214 node_type: "enum".to_string(),
1215 identifier: op.enum_name.clone(),
1216 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1217 location: self.span_to_location(item_enum.span()),
1218 };
1219
1220 let variant_to_remove = item_enum.variants.iter()
1222 .find(|v| v.ident.to_string() == op.variant_name)
1223 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
1224
1225 let start = self.span_to_byte_offset(variant_to_remove.span().start());
1227 let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
1228
1229 while end < self.content.len() {
1231 match self.content.as_bytes()[end] as char {
1232 ',' => {
1233 end += 1;
1234 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
1235 end += 1;
1236 }
1237 break;
1238 }
1239 ' ' | '\t' => end += 1,
1240 '\n' => {
1241 end += 1;
1242 break;
1243 }
1244 _ => break,
1245 }
1246 }
1247
1248 let mut line_start = start;
1250 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1251 line_start -= 1;
1252 }
1253
1254 let before_variant = &self.content[line_start..start];
1255 if before_variant.trim().is_empty() {
1256 self.content.replace_range(line_start..end, "");
1257 } else {
1258 self.content.replace_range(start..end, "");
1259 }
1260
1261 Ok(ModificationResult {
1262 changed: true,
1263 modified_nodes: vec![backup_node],
1264 unmatched_qualified_paths: None,
1265 })
1266 }
1267
1268 pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1269 if op.auto_detect {
1270 self.add_missing_match_arms(op)
1272 } else {
1273 self.add_single_match_arm(op)
1275 }
1276 }
1277
1278 fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1279 let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
1281 let expr: syn::Expr = parse_str(&dummy_match)
1282 .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
1283
1284 let arm = if let syn::Expr::Match(match_expr) = expr {
1286 match_expr.arms.into_iter().next()
1287 .context("Failed to extract arm from dummy match")?
1288 } else {
1289 anyhow::bail!("Expected match expression");
1290 };
1291
1292 let backup_node = if let Some(ref fn_name) = op.function_name {
1294 self.get_function_backup(fn_name)?
1295 } else {
1296 BackupNode {
1299 node_type: "Unknown".to_string(),
1300 identifier: "match_expression".to_string(),
1301 original_content: String::new(),
1302 location: NodeLocation {
1303 line: 0,
1304 column: 0,
1305 end_line: 0,
1306 end_column: 0,
1307 },
1308 }
1309 };
1310
1311 let mut visitor = MatchArmAdder {
1313 target_function: op.function_name.clone(),
1314 arm_to_add: arm,
1315 modified: false,
1316 current_function: None,
1317 modified_function: None,
1318 };
1319
1320 visitor.visit_file_mut(&mut self.syntax_tree);
1321
1322 if visitor.modified {
1323 self.replace_modified_functions(&visitor.modified_function)?;
1325 Ok(ModificationResult {
1326 changed: true,
1327 modified_nodes: vec![backup_node],
1328 unmatched_qualified_paths: None,
1329 })
1330 } else {
1331 Ok(ModificationResult {
1332 changed: false,
1333 modified_nodes: vec![],
1334 unmatched_qualified_paths: None,
1335 })
1336 }
1337 }
1338
1339 fn unparse_item(&self, item: &Item) -> String {
1341 let temp_file = syn::File {
1342 shebang: None,
1343 attrs: Vec::new(),
1344 items: vec![item.clone()],
1345 };
1346 prettyplease::unparse(&temp_file).trim().to_string()
1347 }
1348
1349 fn reformat_item_isolated<F>(&mut self, predicate: F) -> Result<bool>
1352 where
1353 F: Fn(&Item) -> bool,
1354 {
1355 let original_syntax_tree: syn::File = syn::parse_str(&self.content)
1357 .context("Failed to parse original content")?;
1358
1359 let (item_index, original_item) = original_syntax_tree.items.iter()
1360 .enumerate()
1361 .find(|(_, item)| predicate(item))
1362 .ok_or_else(|| anyhow::anyhow!("Item not found"))?;
1363
1364 let start = self.span_to_byte_offset(original_item.span().start());
1366 let end = self.span_to_byte_offset(original_item.span().end());
1367
1368 if item_index >= self.syntax_tree.items.len() {
1370 anyhow::bail!("Item index out of bounds after modification");
1371 }
1372 let modified_item = &self.syntax_tree.items[item_index];
1373
1374 let formatted_item = self.unparse_item(modified_item);
1376
1377 self.content.replace_range(start..end, &formatted_item);
1379
1380 self.syntax_tree = syn::parse_str(&self.content)
1382 .context("Failed to re-parse after isolated prettyplease")?;
1383 self.line_offsets = Self::compute_line_offsets(&self.content);
1384
1385 Ok(true)
1386 }
1387
1388 fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
1390 for item in &self.syntax_tree.items {
1391 if let Item::Fn(f) = item {
1392 if f.sig.ident == fn_name {
1393 return Ok(BackupNode {
1394 node_type: "function".to_string(),
1395 identifier: fn_name.to_string(),
1396 original_content: self.unparse_item(&Item::Fn(f.clone())),
1397 location: self.span_to_location(f.span()),
1398 });
1399 }
1400 }
1401 }
1402 anyhow::bail!("Function '{}' not found", fn_name)
1403 }
1404
1405 fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1406 let enum_name = op.enum_name.as_ref()
1408 .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
1409
1410 let enum_variants = self.find_enum_variants(enum_name)?;
1412
1413 if enum_variants.is_empty() {
1414 anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
1415 }
1416
1417 let existing_patterns = self.find_existing_match_patterns(&op.function_name);
1419
1420 let mut missing_variants = Vec::new();
1422 for variant in &enum_variants {
1423 let pattern = format!("{}::{}", enum_name, variant);
1424 let pattern_normalized = pattern.replace(" ", "");
1425
1426 let exists = existing_patterns.iter().any(|p| {
1427 p.replace(" ", "") == pattern_normalized
1428 });
1429
1430 if !exists {
1431 missing_variants.push(variant.clone());
1432 }
1433 }
1434
1435 if missing_variants.is_empty() {
1436 println!("All enum variants already covered in match expressions");
1437 return Ok(ModificationResult {
1438 changed: false,
1439 modified_nodes: vec![],
1440 unmatched_qualified_paths: None,
1441 });
1442 }
1443
1444 let backup_node = if let Some(ref fn_name) = op.function_name {
1446 self.get_function_backup(fn_name)?
1447 } else {
1448 BackupNode {
1449 node_type: "Unknown".to_string(),
1450 identifier: "match_expression".to_string(),
1451 original_content: String::new(),
1452 location: NodeLocation {
1453 line: 0,
1454 column: 0,
1455 end_line: 0,
1456 end_column: 0,
1457 },
1458 }
1459 };
1460
1461 let mut arms_to_add = Vec::new();
1463 for variant in &missing_variants {
1464 let pattern = format!("{}::{}", enum_name, variant);
1465 let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
1466 let expr: syn::Expr = parse_str(&dummy_match)
1467 .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
1468
1469 if let syn::Expr::Match(match_expr) = expr {
1470 if let Some(arm) = match_expr.arms.into_iter().next() {
1471 arms_to_add.push((pattern.clone(), arm));
1472 }
1473 }
1474 }
1475
1476 let mut visitor = MultiMatchArmAdder {
1478 target_function: op.function_name.clone(),
1479 arms_to_add,
1480 modified: false,
1481 current_function: None,
1482 modified_function: None,
1483 };
1484
1485 visitor.visit_file_mut(&mut self.syntax_tree);
1486
1487 if visitor.modified {
1488 for variant in &missing_variants {
1490 println!("Added match arm for: {}::{}", enum_name, variant);
1491 }
1492
1493 self.replace_modified_functions(&visitor.modified_function)?;
1495 Ok(ModificationResult {
1496 changed: true,
1497 modified_nodes: vec![backup_node],
1498 unmatched_qualified_paths: None,
1499 })
1500 } else {
1501 Ok(ModificationResult {
1502 changed: false,
1503 modified_nodes: vec![],
1504 unmatched_qualified_paths: None,
1505 })
1506 }
1507 }
1508
1509 fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
1510 for item in &self.syntax_tree.items {
1512 if let Item::Enum(e) = item {
1513 if e.ident == enum_name {
1514 let variants: Vec<String> = e.variants.iter()
1515 .map(|v| v.ident.to_string())
1516 .collect();
1517 return Ok(variants);
1518 }
1519 }
1520 }
1521
1522 Ok(Vec::new())
1523 }
1524
1525 fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1526 use syn::visit::Visit;
1527
1528 struct PatternCollector {
1529 target_function: Option<String>,
1530 current_function: Option<String>,
1531 patterns: Vec<String>,
1532 }
1533
1534 impl<'ast> Visit<'ast> for PatternCollector {
1535 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1536 let prev_fn = self.current_function.clone();
1537 self.current_function = Some(node.sig.ident.to_string());
1538 syn::visit::visit_item_fn(self, node);
1539 self.current_function = prev_fn;
1540 }
1541
1542 fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1543 if let Some(ref target) = self.target_function {
1545 if self.current_function.as_ref() != Some(target) {
1546 syn::visit::visit_expr_match(self, node);
1547 return;
1548 }
1549 }
1550
1551 for arm in &node.arms {
1553 self.patterns.push(arm.pat.to_token_stream().to_string());
1554 }
1555
1556 syn::visit::visit_expr_match(self, node);
1557 }
1558 }
1559
1560 let mut collector = PatternCollector {
1561 target_function: function_name.clone(),
1562 current_function: None,
1563 patterns: Vec::new(),
1564 };
1565
1566 collector.visit_file(&self.syntax_tree);
1567 collector.patterns
1568 }
1569
1570 pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1571 let backup_node = if let Some(ref fn_name) = op.function_name {
1573 self.get_function_backup(fn_name)?
1574 } else {
1575 BackupNode {
1576 node_type: "Unknown".to_string(),
1577 identifier: "match_expression".to_string(),
1578 original_content: String::new(),
1579 location: NodeLocation {
1580 line: 0,
1581 column: 0,
1582 end_line: 0,
1583 end_column: 0,
1584 },
1585 }
1586 };
1587
1588 let new_body: syn::Expr = parse_str(&op.new_body)
1590 .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1591
1592 let mut visitor = MatchArmUpdater {
1594 target_function: op.function_name.clone(),
1595 pattern_to_match: op.pattern.clone(),
1596 new_body,
1597 modified: false,
1598 current_function: None,
1599 modified_function: None,
1600 };
1601
1602 visitor.visit_file_mut(&mut self.syntax_tree);
1603
1604 if visitor.modified {
1605 self.replace_modified_functions(&visitor.modified_function)?;
1607 Ok(ModificationResult {
1608 changed: true,
1609 modified_nodes: vec![backup_node],
1610 unmatched_qualified_paths: None,
1611 })
1612 } else {
1613 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1614 }
1615 }
1616
1617 pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1618 let backup_node = if let Some(ref fn_name) = op.function_name {
1620 self.get_function_backup(fn_name)?
1621 } else {
1622 BackupNode {
1623 node_type: "Unknown".to_string(),
1624 identifier: "match_expression".to_string(),
1625 original_content: String::new(),
1626 location: NodeLocation {
1627 line: 0,
1628 column: 0,
1629 end_line: 0,
1630 end_column: 0,
1631 },
1632 }
1633 };
1634
1635 let mut visitor = MatchArmRemover {
1637 target_function: op.function_name.clone(),
1638 pattern_to_remove: op.pattern.clone(),
1639 modified: false,
1640 current_function: None,
1641 modified_function: None,
1642 };
1643
1644 visitor.visit_file_mut(&mut self.syntax_tree);
1645
1646 if visitor.modified {
1647 self.replace_modified_functions(&visitor.modified_function)?;
1649 Ok(ModificationResult {
1650 changed: true,
1651 modified_nodes: vec![backup_node],
1652 unmatched_qualified_paths: None,
1653 })
1654 } else {
1655 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1656 }
1657 }
1658
1659 pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1660 let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1662 let dummy: syn::ItemImpl = parse_str(&method_code)
1663 .context("Failed to parse method definition")?;
1664
1665 let new_method = dummy.items.first()
1666 .context("No method found in definition")?
1667 .clone();
1668
1669 let method_name = match &new_method {
1671 syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1672 _ => anyhow::bail!("Only method definitions are supported"),
1673 };
1674
1675 let impl_index = self.syntax_tree.items.iter().position(|item| {
1677 if let Item::Impl(impl_block) = item {
1678 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1680 if let Some(segment) = type_path.path.segments.last() {
1681 return segment.ident == op.target;
1682 }
1683 }
1684 }
1685 false
1686 }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1687
1688 let impl_block = match &self.syntax_tree.items[impl_index] {
1690 Item::Impl(i) => i,
1691 _ => unreachable!(),
1692 };
1693
1694 let method_exists = impl_block.items.iter().any(|item| {
1695 if let syn::ImplItem::Fn(f) = item {
1696 f.sig.ident == method_name
1697 } else {
1698 false
1699 }
1700 });
1701
1702 if method_exists {
1703 return Ok(ModificationResult {
1704 changed: false,
1705 modified_nodes: vec![],
1706 unmatched_qualified_paths: None,
1707 });
1708 }
1709
1710 let backup_node = BackupNode {
1712 node_type: "ItemImpl".to_string(),
1713 identifier: op.target.clone(),
1714 original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1715 location: self.span_to_location(impl_block.span()),
1716 };
1717
1718 let impl_span = impl_block.span();
1720
1721 match &mut self.syntax_tree.items[impl_index] {
1723 Item::Impl(impl_block) => {
1724 match &op.position {
1726 InsertPosition::First => {
1727 impl_block.items.insert(0, new_method);
1728 }
1729 InsertPosition::Last => {
1730 impl_block.items.push(new_method);
1731 }
1732 InsertPosition::After(name) => {
1733 let pos = impl_block.items.iter().position(|item| {
1734 if let syn::ImplItem::Fn(f) = item {
1735 f.sig.ident == name
1736 } else {
1737 false
1738 }
1739 }).with_context(|| format!("Method '{}' not found", name))?;
1740 impl_block.items.insert(pos + 1, new_method);
1741 }
1742 InsertPosition::Before(name) => {
1743 let pos = impl_block.items.iter().position(|item| {
1744 if let syn::ImplItem::Fn(f) = item {
1745 f.sig.ident == name
1746 } else {
1747 false
1748 }
1749 }).with_context(|| format!("Method '{}' not found", name))?;
1750 impl_block.items.insert(pos, new_method);
1751 }
1752 }
1753 }
1754 _ => unreachable!(),
1755 }
1756
1757 self.replace_formatted_item(impl_index, impl_span)?;
1759
1760 Ok(ModificationResult {
1761 changed: true,
1762 modified_nodes: vec![backup_node],
1763 unmatched_qualified_paths: None,
1764 })
1765 }
1766
1767 pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1768 let use_code = format!("use {};", op.use_path);
1770 let use_item: syn::ItemUse = parse_str(&use_code)
1771 .context("Failed to parse use statement")?;
1772
1773 let use_exists = self.syntax_tree.items.iter().any(|item| {
1775 if let Item::Use(existing_use) = item {
1776 existing_use.tree.to_token_stream().to_string() ==
1778 use_item.tree.to_token_stream().to_string()
1779 } else {
1780 false
1781 }
1782 });
1783
1784 if use_exists {
1785 return Ok(ModificationResult {
1786 changed: false,
1787 modified_nodes: vec![],
1788 unmatched_qualified_paths: None,
1789 });
1790 }
1791
1792 let backup_node = BackupNode {
1794 node_type: "ItemUse".to_string(),
1795 identifier: op.use_path.clone(),
1796 original_content: format!("use {};", op.use_path),
1797 location: NodeLocation {
1798 line: 0,
1799 column: 0,
1800 end_line: 0,
1801 end_column: 0,
1802 },
1803 };
1804
1805 let insert_index = match &op.position {
1807 InsertPosition::First => 0,
1808 InsertPosition::Last => {
1809 self.syntax_tree.items.iter()
1811 .rposition(|item| matches!(item, Item::Use(_)))
1812 .map(|i| i + 1)
1813 .unwrap_or(0)
1814 }
1815 InsertPosition::After(path) => {
1816 let pos = self.syntax_tree.items.iter().position(|item| {
1818 if let Item::Use(u) = item {
1819 u.tree.to_token_stream().to_string().contains(path)
1820 } else {
1821 false
1822 }
1823 }).with_context(|| format!("Use statement for '{}' not found", path))?;
1824 pos + 1
1825 }
1826 InsertPosition::Before(path) => {
1827 self.syntax_tree.items.iter().position(|item| {
1829 if let Item::Use(u) = item {
1830 u.tree.to_token_stream().to_string().contains(path)
1831 } else {
1832 false
1833 }
1834 }).with_context(|| format!("Use statement for '{}' not found", path))?
1835 }
1836 };
1837
1838 self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1840
1841 let insert_line_pos = if insert_index == 0 {
1844 0
1846 } else {
1847 let prev_item = &self.syntax_tree.items[insert_index - 1];
1849 let span = prev_item.span();
1850 let end_pos = self.span_to_byte_offset(span.end());
1851
1852 let mut line_end = end_pos;
1854 while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1855 line_end += 1;
1856 }
1857 if line_end < self.content.len() {
1859 line_end + 1
1860 } else {
1861 self.content.push('\n');
1863 self.content.len()
1864 }
1865 };
1866
1867 let use_str = format!("use {};\n", op.use_path);
1869
1870 self.content.insert_str(insert_line_pos, &use_str);
1872
1873 Ok(ModificationResult {
1874 changed: true,
1875 modified_nodes: vec![backup_node],
1876 unmatched_qualified_paths: None,
1877 })
1878 }
1879
1880 pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1881 let item_index = self.syntax_tree.items.iter().position(|item| {
1883 match (&op.target_type as &str, item) {
1884 ("struct", Item::Struct(s)) => s.ident == op.target_name,
1885 ("enum", Item::Enum(e)) => e.ident == op.target_name,
1886 _ => false,
1887 }
1888 }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1889
1890 let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1892 Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1893 Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1894 _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1895 };
1896
1897 if let Some(ref where_filter) = op.where_filter {
1899 if !self.matches_where_filter(item_attrs, where_filter)? {
1900 return Ok(ModificationResult {
1902 changed: false,
1903 modified_nodes: vec![],
1904 unmatched_qualified_paths: None,
1905 });
1906 }
1907 }
1908
1909 let backup_node = BackupNode {
1911 node_type: if op.target_type == "struct" { "struct" } else { "enum" }.to_string(),
1912 identifier: op.target_name.clone(),
1913 original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1914 location: self.span_to_location(item_span),
1915 };
1916
1917 let new_derives: Vec<String> = op.derives.iter()
1919 .filter(|d| !existing_derives.contains(&d.to_string()))
1920 .cloned()
1921 .collect();
1922
1923 if new_derives.is_empty() {
1924 return Ok(ModificationResult {
1926 changed: false,
1927 modified_nodes: vec![],
1928 unmatched_qualified_paths: None,
1929 });
1930 }
1931
1932 let mut all_derives = existing_derives;
1934 all_derives.extend(new_derives);
1935
1936 let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1938
1939 match &mut self.syntax_tree.items[item_index] {
1941 Item::Struct(s) => {
1942 Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1943 }
1944 Item::Enum(e) => {
1945 Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1946 }
1947 _ => unreachable!(),
1948 }
1949
1950 self.replace_formatted_item(item_index, item_span)?;
1952
1953 Ok(ModificationResult {
1954 changed: true,
1955 modified_nodes: vec![backup_node],
1956 unmatched_qualified_paths: None,
1957 })
1958 }
1959
1960 fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1962 let item_start_pos = self.span_to_byte_offset(original_span.start());
1964 let item_end_pos = self.span_to_byte_offset(original_span.end());
1965
1966 let mut actual_start = item_start_pos;
1968
1969 let mut temp_pos = item_start_pos;
1971 while temp_pos > 0 {
1972 temp_pos = temp_pos.saturating_sub(1);
1974 let mut line_start = temp_pos;
1975 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1976 line_start -= 1;
1977 }
1978
1979 let line = if temp_pos < self.content.len() {
1980 &self.content[line_start..temp_pos + 1]
1981 } else {
1982 &self.content[line_start..]
1983 };
1984 let trimmed = line.trim();
1985
1986 if trimmed.starts_with("#[") {
1987 actual_start = line_start;
1988 temp_pos = line_start;
1989 } else if trimmed.is_empty() {
1990 temp_pos = line_start;
1991 } else {
1992 break;
1993 }
1994
1995 if line_start == 0 {
1996 break;
1997 }
1998 }
1999
2000 let item_clone = self.syntax_tree.items[item_index].clone();
2002 let temp_file = syn::File {
2003 shebang: None,
2004 attrs: Vec::new(),
2005 items: vec![item_clone],
2006 };
2007
2008 let formatted = prettyplease::unparse(&temp_file);
2010 let formatted = formatted.trim();
2011
2012 self.content.replace_range(actual_start..item_end_pos, formatted);
2014
2015 Ok(())
2016 }
2017
2018 fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
2020 for attr in attrs {
2021 if attr.path().is_ident("derive") {
2022 if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
2023 let tokens_str = meta_list.tokens.to_string();
2024 return tokens_str
2025 .split(',')
2026 .map(|s| s.trim().to_string())
2027 .collect();
2028 }
2029 }
2030 }
2031 Vec::new()
2032 }
2033
2034 fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
2039 if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
2041 let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
2042 let existing_derives = Self::extract_derives(attrs);
2043
2044 for required_trait in required_traits {
2046 if existing_derives.iter().any(|d| d == required_trait) {
2047 return Ok(true);
2048 }
2049 }
2050 return Ok(false);
2051 }
2052
2053 Ok(true)
2055 }
2056
2057 fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
2059 let derive_str = derives.join(", ");
2060
2061 let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
2063 let parsed: syn::ItemStruct = parse_str(&dummy)
2064 .context("Failed to parse derive attribute")?;
2065
2066 let new_attr = parsed.attrs.into_iter()
2067 .find(|a| a.path().is_ident("derive"))
2068 .context("Failed to extract derive attribute")?;
2069
2070 if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
2072 attrs[pos] = new_attr;
2073 } else {
2074 attrs.insert(0, new_attr);
2076 }
2077
2078 Ok(())
2079 }
2080
2081 fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
2083 if modified_function.is_none() {
2085 self.content = prettyplease::unparse(&self.syntax_tree);
2088 return Ok(());
2089 }
2090
2091 let original_syntax_tree: File = syn::parse_str(&self.content)
2093 .context("Failed to re-parse original content")?;
2094
2095 let function_name = modified_function.as_ref().unwrap();
2096
2097 let original_fn = original_syntax_tree.items.iter()
2099 .find_map(|item| {
2100 if let Item::Fn(f) = item {
2101 if f.sig.ident == function_name {
2102 return Some(f.clone());
2103 }
2104 }
2105 None
2106 })
2107 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
2108
2109 let start = self.span_to_byte_offset(original_fn.span().start());
2111 let end = self.span_to_byte_offset(original_fn.span().end());
2112
2113 let modified_fn = self.syntax_tree.items.iter()
2115 .find_map(|item| {
2116 if let Item::Fn(f) = item {
2117 if f.sig.ident == function_name {
2118 return Some(f.clone());
2119 }
2120 }
2121 None
2122 })
2123 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
2124
2125 let dummy_file = syn::File {
2127 shebang: None,
2128 attrs: Vec::new(),
2129 items: vec![Item::Fn(modified_fn)],
2130 };
2131
2132 let formatted_fn = prettyplease::unparse(&dummy_file);
2133
2134 let formatted_fn = formatted_fn.trim();
2136
2137 self.content.replace_range(start..end, formatted_fn);
2139
2140 Ok(())
2141 }
2142
2143 pub fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
2144 let line_idx = pos.line.saturating_sub(1);
2145 if line_idx < self.line_offsets.len() {
2146 self.line_offsets[line_idx] + pos.column
2147 } else {
2148 self.content.len()
2149 }
2150 }
2151
2152 fn find_after_field_end(&self, pos: usize) -> usize {
2153 let mut i = pos;
2155 while i < self.content.len() {
2156 match self.content.as_bytes()[i] as char {
2157 ',' => return i + 1,
2158 '\n' => return i + 1,
2159 _ => i += 1,
2160 }
2161 }
2162 pos
2163 }
2164
2165 fn get_indentation(&self, pos: usize) -> String {
2166 let mut line_start = pos;
2168 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
2169 line_start -= 1;
2170 }
2171
2172 let mut indent = String::new();
2174 let mut i = line_start;
2175 while i < self.content.len() {
2176 match self.content.as_bytes()[i] as char {
2177 ' ' | '\t' => {
2178 indent.push(self.content.as_bytes()[i] as char);
2179 i += 1;
2180 }
2181 _ => break,
2182 }
2183 }
2184
2185 if indent.is_empty() {
2187 " ".to_string()
2188 } else {
2189 indent
2190 }
2191 }
2192
2193 pub fn to_string(&self) -> String {
2194 self.content.clone()
2195 }
2196
2197 pub fn get_syntax_tree(&self) -> &syn::File {
2199 &self.syntax_tree
2200 }
2201
2202 pub fn replace_range(&mut self, start: usize, end: usize, new_content: &str) -> Result<()> {
2204 if start > end || end > self.content.len() {
2205 anyhow::bail!("Invalid range: {}..{} (content length: {})", start, end, self.content.len());
2206 }
2207
2208 self.content.replace_range(start..end, new_content);
2209
2210 self.syntax_tree = syn::parse_str(&self.content)
2212 .context("Failed to re-parse after replace_range")?;
2213 self.line_offsets = Self::compute_line_offsets(&self.content);
2214
2215 Ok(())
2216 }
2217
2218 pub fn find_field_locations(&self, field_name: &str) -> Result<Vec<crate::operations::FieldLocation>> {
2220 use syn::visit::Visit;
2221 use crate::operations::{FieldLocation, FieldContext};
2222
2223 let mut locations = Vec::new();
2224
2225 for item in &self.syntax_tree.items {
2227 if let Item::Struct(s) = item {
2228 if let Fields::Named(ref fields) = s.fields {
2229 for field in &fields.named {
2230 if let Some(ident) = &field.ident {
2231 if ident == field_name {
2232 let field_type = quote::quote!(#field).to_string()
2233 .split(':')
2234 .nth(1)
2235 .map(|s| s.trim().to_string())
2236 .unwrap_or_else(|| "unknown".to_string());
2237 locations.push(FieldLocation {
2238 file_path: String::new(),
2239 line: s.span().start().line,
2240 context: FieldContext::StructDefinition {
2241 struct_name: s.ident.to_string(),
2242 field_type,
2243 },
2244 });
2245 }
2246 }
2247 }
2248 }
2249 }
2250
2251 if let Item::Enum(e) = item {
2253 for variant in &e.variants {
2254 if let Fields::Named(ref fields) = variant.fields {
2255 for field in &fields.named {
2256 if let Some(ident) = &field.ident {
2257 if ident == field_name {
2258 let field_type = quote::quote!(#field).to_string()
2259 .split(':')
2260 .nth(1)
2261 .map(|s| s.trim().to_string())
2262 .unwrap_or_else(|| "unknown".to_string());
2263 locations.push(FieldLocation {
2264 file_path: String::new(),
2265 line: variant.span().start().line,
2266 context: FieldContext::EnumVariantDefinition {
2267 enum_name: e.ident.to_string(),
2268 variant_name: variant.ident.to_string(),
2269 field_type,
2270 },
2271 });
2272 }
2273 }
2274 }
2275 }
2276 }
2277 }
2278 }
2279
2280 struct LiteralVisitor<'a> {
2282 field_name: &'a str,
2283 locations: Vec<FieldLocation>,
2284 }
2285
2286 impl<'ast, 'a> Visit<'ast> for LiteralVisitor<'a> {
2287 fn visit_expr(&mut self, node: &'ast Expr) {
2288 if let Expr::Struct(expr_struct) = node {
2289 for field_value in &expr_struct.fields {
2291 if let syn::Member::Named(ident) = &field_value.member {
2292 if ident == self.field_name {
2293 let struct_name = expr_struct.path.segments.iter()
2294 .map(|seg| seg.ident.to_string())
2295 .collect::<Vec<_>>()
2296 .join("::");
2297
2298 self.locations.push(FieldLocation {
2299 file_path: String::new(),
2300 line: expr_struct.span().start().line,
2301 context: FieldContext::StructLiteral {
2302 struct_name,
2303 },
2304 });
2305 break;
2306 }
2307 }
2308 }
2309 }
2310 syn::visit::visit_expr(self, node);
2311 }
2312 }
2313
2314 let mut visitor = LiteralVisitor {
2315 field_name,
2316 locations: Vec::new(),
2317 };
2318
2319 visitor.visit_file(&self.syntax_tree);
2320 locations.extend(visitor.locations);
2321
2322 Ok(locations)
2323 }
2324
2325 pub fn inspect(&self, node_type: Option<&str>, name_filter: Option<&str>, variant_filter: Option<&str>, include_comments: bool) -> Result<Vec<crate::operations::InspectResult>> {
2327 use syn::visit::Visit;
2328 use crate::operations::InspectResult;
2329
2330 let mut results = Vec::new();
2331
2332 if node_type.is_none() {
2334 let all_types = vec![
2335 "struct", "enum", "function", "impl-method", "trait", "const", "static", "type-alias", "mod",
2336 "struct-literal", "match-arm", "enum-usage", "function-call", "method-call", "macro-call", "identifier", "type-ref",
2337 ];
2338 for nt in all_types {
2339 let mut type_results = self.inspect(Some(nt), name_filter, variant_filter, include_comments)?;
2340 results.append(&mut type_results);
2341 }
2342 return Ok(results);
2343 }
2344
2345 let node_type = node_type.unwrap(); match node_type {
2348 "struct-literal" => {
2349 struct StructLiteralVisitor<'a> {
2351 results: &'a mut Vec<InspectResult>,
2352 name_filter: Option<&'a str>,
2353 editor: &'a RustEditor,
2354 include_comments: bool,
2355 }
2356
2357 impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
2358 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2359 use syn::parse::Parser;
2362
2363 if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
2365 .parse2(node.mac.tokens.clone())
2366 {
2367 for expr in exprs.iter() {
2368 syn::visit::visit_expr(self, expr);
2369 }
2370 }
2371
2372 syn::visit::visit_expr_macro(self, node);
2374 }
2375
2376 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
2377 let filter = match self.name_filter {
2383 Some(f) => f,
2384 None => {
2385 let struct_name = if node.path.segments.len() > 1 {
2388 node.path.segments.iter()
2389 .map(|seg| seg.ident.to_string())
2390 .collect::<Vec<_>>()
2391 .join("::")
2392 } else {
2393 node.path.segments.last()
2394 .map(|seg| seg.ident.to_string())
2395 .unwrap_or_default()
2396 };
2397
2398 let snippet = self.editor.format_expr_struct(node);
2399 let location = self.editor.span_to_location(node.span());
2400
2401 let preceding_comment = if self.include_comments {
2403 extract_preceding_comment(&self.editor.content, location.line)
2404 } else {
2405 None
2406 };
2407
2408 let preceding_comment = if self.include_comments {
2410 extract_preceding_comment(&self.editor.content, location.line)
2411 } else {
2412 None
2413 };
2414
2415 self.results.push(InspectResult {
2416 file_path: String::new(),
2417 node_type: "struct-literal".to_string(),
2418 identifier: struct_name,
2419 location,
2420 snippet,
2421 preceding_comment,
2422 });
2423
2424 syn::visit::visit_expr_struct(self, node);
2425 return;
2426 }
2427 };
2428
2429 let matches = if filter.contains("::") {
2431 if filter.starts_with("*::") {
2433 let target_name = &filter[3..]; node.path.segments.last()
2436 .map(|seg| seg.ident.to_string() == target_name)
2437 .unwrap_or(false)
2438 } else {
2439 let path_str = node.path.segments.iter()
2441 .map(|seg| seg.ident.to_string())
2442 .collect::<Vec<_>>()
2443 .join("::");
2444 path_str == filter
2445 }
2446 } else {
2447 node.path.segments.last()
2450 .map(|seg| seg.ident.to_string() == filter)
2451 .unwrap_or(false)
2452 };
2453
2454 if !matches {
2455 syn::visit::visit_expr_struct(self, node);
2456 return;
2457 }
2458
2459 let struct_name = if node.path.segments.len() > 1 {
2462 node.path.segments.iter()
2463 .map(|seg| seg.ident.to_string())
2464 .collect::<Vec<_>>()
2465 .join("::")
2466 } else {
2467 node.path.segments.last()
2468 .map(|seg| seg.ident.to_string())
2469 .unwrap_or_default()
2470 };
2471
2472 let snippet = self.editor.format_expr_struct(node);
2474 let location = self.editor.span_to_location(node.span());
2475
2476 let preceding_comment = if self.include_comments {
2478 extract_preceding_comment(&self.editor.content, location.line)
2479 } else {
2480 None
2481 };
2482
2483 let preceding_comment = if self.include_comments {
2485 extract_preceding_comment(&self.editor.content, location.line)
2486 } else {
2487 None
2488 };
2489
2490 self.results.push(InspectResult {
2491 file_path: String::new(), node_type: "struct-literal".to_string(),
2493 identifier: struct_name,
2494 location,
2495 snippet,
2496 preceding_comment,
2497 });
2498
2499 syn::visit::visit_expr_struct(self, node);
2501 }
2502 }
2503
2504 let mut visitor = StructLiteralVisitor {
2505 results: &mut results,
2506 name_filter,
2507 editor: self,
2508 include_comments,
2509 };
2510
2511 for item in &self.syntax_tree.items {
2513 syn::visit::visit_item(&mut visitor, item);
2514 }
2515 }
2516 "match-arm" => {
2517 struct MatchArmVisitor<'a> {
2519 results: &'a mut Vec<InspectResult>,
2520 pattern_filter: Option<&'a str>,
2521 editor: &'a RustEditor,
2522 include_comments: bool,
2523 }
2524
2525 impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
2526 fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
2527 for arm in &node.arms {
2529 let pat = &arm.pat;
2531 let pattern_str = quote::quote!(#pat).to_string();
2532
2533 if let Some(filter) = self.pattern_filter {
2535 let normalized_pattern = pattern_str.replace(" ", "");
2537 let normalized_filter = filter.replace(" ", "");
2538
2539 if !normalized_pattern.contains(&normalized_filter) {
2540 continue;
2541 }
2542 }
2543
2544 let snippet = self.editor.format_match_arm(arm);
2546 let location = self.editor.span_to_location(arm.span());
2547
2548 let preceding_comment = if self.include_comments {
2550 extract_preceding_comment(&self.editor.content, location.line)
2551 } else {
2552 None
2553 };
2554
2555 let preceding_comment = if self.include_comments {
2557 extract_preceding_comment(&self.editor.content, location.line)
2558 } else {
2559 None
2560 };
2561
2562 self.results.push(InspectResult {
2563 file_path: String::new(), node_type: "match-arm".to_string(),
2565 identifier: pattern_str.replace(" ", ""),
2566 location,
2567 snippet,
2568 preceding_comment,
2569 });
2570 }
2571
2572 syn::visit::visit_expr_match(self, node);
2574 }
2575 }
2576
2577 let mut visitor = MatchArmVisitor {
2578 results: &mut results,
2579 pattern_filter: name_filter,
2580 editor: self,
2581 include_comments,
2582 };
2583
2584 for item in &self.syntax_tree.items {
2586 syn::visit::visit_item(&mut visitor, item);
2587 }
2588 }
2589 "enum-usage" => {
2590 struct EnumUsageVisitor<'a> {
2592 results: &'a mut Vec<InspectResult>,
2593 path_filter: Option<&'a str>,
2594 editor: &'a RustEditor,
2595 include_comments: bool,
2596 }
2597
2598 impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
2599 fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
2600 let path = &node.path;
2602 let path_str = quote::quote!(#path).to_string();
2603
2604 if let Some(filter) = self.path_filter {
2606 let normalized_path = path_str.replace(" ", "");
2608 let normalized_filter = filter.replace(" ", "");
2609
2610 if !normalized_path.contains(&normalized_filter) {
2611 syn::visit::visit_expr_path(self, node);
2612 return;
2613 }
2614 }
2615
2616 let snippet = self.editor.format_expr_path(node);
2618 let location = self.editor.span_to_location(node.span());
2619
2620 let preceding_comment = if self.include_comments {
2622 extract_preceding_comment(&self.editor.content, location.line)
2623 } else {
2624 None
2625 };
2626
2627 let preceding_comment = if self.include_comments {
2629 extract_preceding_comment(&self.editor.content, location.line)
2630 } else {
2631 None
2632 };
2633
2634 self.results.push(InspectResult {
2635 file_path: String::new(), node_type: "enum-usage".to_string(),
2637 identifier: path_str.replace(" ", ""),
2638 location,
2639 snippet,
2640 preceding_comment,
2641 });
2642
2643 syn::visit::visit_expr_path(self, node);
2645 }
2646 }
2647
2648 let mut visitor = EnumUsageVisitor {
2649 results: &mut results,
2650 path_filter: name_filter,
2651 editor: self,
2652 include_comments,
2653 };
2654
2655 for item in &self.syntax_tree.items {
2657 syn::visit::visit_item(&mut visitor, item);
2658 }
2659 }
2660 "function-call" => {
2661 struct FunctionCallVisitor<'a> {
2663 results: &'a mut Vec<InspectResult>,
2664 name_filter: Option<&'a str>,
2665 editor: &'a RustEditor,
2666 include_comments: bool,
2667 }
2668
2669 impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
2670 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
2671 let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
2673 expr_path.path.segments.last()
2675 .map(|seg| seg.ident.to_string())
2676 .unwrap_or_default()
2677 } else {
2678 quote::quote!(#node.func).to_string()
2680 };
2681
2682 if let Some(filter) = self.name_filter {
2684 if func_name != filter {
2685 syn::visit::visit_expr_call(self, node);
2686 return;
2687 }
2688 }
2689
2690 let snippet = self.editor.format_expr_call(node);
2692 let location = self.editor.span_to_location(node.span());
2693
2694 let preceding_comment = if self.include_comments {
2696 extract_preceding_comment(&self.editor.content, location.line)
2697 } else {
2698 None
2699 };
2700
2701 let preceding_comment = if self.include_comments {
2703 extract_preceding_comment(&self.editor.content, location.line)
2704 } else {
2705 None
2706 };
2707
2708 self.results.push(InspectResult {
2709 file_path: String::new(), node_type: "function-call".to_string(),
2711 identifier: func_name,
2712 location,
2713 snippet,
2714 preceding_comment,
2715 });
2716
2717 syn::visit::visit_expr_call(self, node);
2719 }
2720 }
2721
2722 let mut visitor = FunctionCallVisitor {
2723 results: &mut results,
2724 name_filter,
2725 editor: self,
2726 include_comments,
2727 };
2728
2729 for item in &self.syntax_tree.items {
2731 syn::visit::visit_item(&mut visitor, item);
2732 }
2733 }
2734 "trait-method" => {
2735 struct TraitMethodVisitor<'a> {
2737 results: &'a mut Vec<InspectResult>,
2738 name_filter: Option<&'a str>,
2739 editor: &'a RustEditor,
2740 include_comments: bool,
2741 current_trait_name: Option<String>,
2742 }
2743
2744 impl<'ast, 'a> Visit<'ast> for TraitMethodVisitor<'a> {
2745 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
2746 let prev_trait_name = self.current_trait_name.clone();
2748 self.current_trait_name = Some(node.ident.to_string());
2749
2750 syn::visit::visit_item_trait(self, node);
2751
2752 self.current_trait_name = prev_trait_name;
2753 }
2754
2755 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
2756 let method_name = node.sig.ident.to_string();
2757
2758 let identifier = if let Some(ref trait_name) = self.current_trait_name {
2760 format!("{}::{}", trait_name, method_name)
2761 } else {
2762 method_name.clone()
2763 };
2764
2765 if let Some(filter) = self.name_filter {
2767 if !identifier.contains(filter) && method_name != filter {
2769 syn::visit::visit_trait_item_fn(self, node);
2770 return;
2771 }
2772 }
2773
2774 let snippet = self.editor.format_trait_item_fn(node);
2776 let location = self.editor.span_to_location(node.span());
2777
2778 let preceding_comment = if self.include_comments {
2780 extract_preceding_comment(&self.editor.content, location.line)
2781 } else {
2782 None
2783 };
2784
2785 self.results.push(InspectResult {
2786 file_path: String::new(),
2787 node_type: "trait-method".to_string(),
2788 identifier,
2789 location,
2790 snippet,
2791 preceding_comment,
2792 });
2793
2794 syn::visit::visit_trait_item_fn(self, node);
2795 }
2796 }
2797
2798 let mut visitor = TraitMethodVisitor {
2799 results: &mut results,
2800 name_filter,
2801 editor: self,
2802 include_comments,
2803 current_trait_name: None,
2804 };
2805
2806 for item in &self.syntax_tree.items {
2807 syn::visit::visit_item(&mut visitor, item);
2808 }
2809 }
2810 "method-call" => {
2811 struct MethodCallVisitor<'a> {
2813 results: &'a mut Vec<InspectResult>,
2814 name_filter: Option<&'a str>,
2815 editor: &'a RustEditor,
2816 include_comments: bool,
2817 }
2818
2819 impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
2820 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
2821 let method_name = node.method.to_string();
2823
2824 if let Some(filter) = self.name_filter {
2826 if method_name != filter {
2827 syn::visit::visit_expr_method_call(self, node);
2828 return;
2829 }
2830 }
2831
2832 let snippet = self.editor.format_expr_method_call(node);
2834 let location = self.editor.span_to_location(node.span());
2835
2836 let preceding_comment = if self.include_comments {
2838 extract_preceding_comment(&self.editor.content, location.line)
2839 } else {
2840 None
2841 };
2842
2843 self.results.push(InspectResult {
2844 file_path: String::new(), node_type: "method-call".to_string(),
2846 identifier: method_name,
2847 location,
2848 snippet,
2849 preceding_comment,
2850 });
2851
2852 syn::visit::visit_expr_method_call(self, node);
2854 }
2855 }
2856
2857 let mut visitor = MethodCallVisitor {
2858 results: &mut results,
2859 name_filter,
2860 editor: self,
2861 include_comments,
2862 };
2863
2864 for item in &self.syntax_tree.items {
2866 syn::visit::visit_item(&mut visitor, item);
2867 }
2868 }
2869 "identifier" => {
2870 struct IdentifierVisitor<'a> {
2872 results: &'a mut Vec<InspectResult>,
2873 name_filter: Option<&'a str>,
2874 editor: &'a RustEditor,
2875 include_comments: bool,
2876 }
2877
2878 impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2879 fn visit_ident(&mut self, node: &'ast syn::Ident) {
2880 let ident_name = node.to_string();
2882
2883 if let Some(filter) = self.name_filter {
2885 if ident_name != filter {
2886 syn::visit::visit_ident(self, node);
2887 return;
2888 }
2889 }
2890
2891 let snippet = self.editor.format_ident(node);
2893 let location = self.editor.span_to_location(node.span());
2894
2895 let preceding_comment = if self.include_comments {
2897 extract_preceding_comment(&self.editor.content, location.line)
2898 } else {
2899 None
2900 };
2901
2902 self.results.push(InspectResult {
2903 file_path: String::new(), node_type: "identifier".to_string(),
2905 identifier: ident_name,
2906 location,
2907 snippet,
2908 preceding_comment,
2909 });
2910
2911 syn::visit::visit_ident(self, node);
2913 }
2914 }
2915
2916 let mut visitor = IdentifierVisitor {
2917 results: &mut results,
2918 name_filter,
2919 editor: self,
2920 include_comments,
2921 };
2922
2923 for item in &self.syntax_tree.items {
2925 syn::visit::visit_item(&mut visitor, item);
2926 }
2927 }
2928 "type-ref" => {
2929 struct TypeRefVisitor<'a> {
2931 results: &'a mut Vec<InspectResult>,
2932 name_filter: Option<&'a str>,
2933 editor: &'a RustEditor,
2934 include_comments: bool,
2935 }
2936
2937 impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2938 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2939 let type_name = node.path.segments.last()
2941 .map(|seg| seg.ident.to_string())
2942 .unwrap_or_default();
2943
2944 if let Some(filter) = self.name_filter {
2946 if type_name != filter {
2947 syn::visit::visit_type_path(self, node);
2948 return;
2949 }
2950 }
2951
2952 let snippet = self.editor.format_type_path(node);
2954 let location = self.editor.span_to_location(node.span());
2955
2956 let preceding_comment = if self.include_comments {
2958 extract_preceding_comment(&self.editor.content, location.line)
2959 } else {
2960 None
2961 };
2962
2963 let path = &node.path;
2965 let path_str = quote::quote!(#path).to_string();
2966
2967 self.results.push(InspectResult {
2968 file_path: String::new(), node_type: "type-ref".to_string(),
2970 identifier: path_str.replace(" ", ""),
2971 location,
2972 snippet,
2973 preceding_comment,
2974 });
2975
2976 syn::visit::visit_type_path(self, node);
2978 }
2979 }
2980
2981 let mut visitor = TypeRefVisitor {
2982 results: &mut results,
2983 name_filter,
2984 editor: self,
2985 include_comments,
2986 };
2987
2988 for item in &self.syntax_tree.items {
2990 syn::visit::visit_item(&mut visitor, item);
2991 }
2992 }
2993 "macro-call" => {
2994 struct MacroCallVisitor<'a> {
2996 results: &'a mut Vec<InspectResult>,
2997 name_filter: Option<&'a str>,
2998 editor: &'a RustEditor,
2999 include_comments: bool,
3000 }
3001
3002 impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
3003 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
3004 let macro_name = node.mac.path.segments.last()
3006 .map(|seg| seg.ident.to_string())
3007 .unwrap_or_default();
3008
3009 if let Some(filter) = self.name_filter {
3011 if macro_name != filter {
3012 syn::visit::visit_expr_macro(self, node);
3013 return;
3014 }
3015 }
3016
3017 let snippet = self.editor.format_expr_macro(node);
3019 let location = self.editor.span_to_location(node.span());
3020
3021 let preceding_comment = if self.include_comments {
3023 extract_preceding_comment(&self.editor.content, location.line)
3024 } else {
3025 None
3026 };
3027
3028 self.results.push(InspectResult {
3029 file_path: String::new(), node_type: "macro-call".to_string(),
3031 identifier: macro_name,
3032 location,
3033 snippet,
3034 preceding_comment,
3035 });
3036
3037 syn::visit::visit_expr_macro(self, node);
3039 }
3040
3041 fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
3042 if let syn::Stmt::Macro(macro_stmt) = node {
3044 let macro_name = macro_stmt.mac.path.segments.last()
3045 .map(|seg| seg.ident.to_string())
3046 .unwrap_or_default();
3047
3048 if let Some(filter) = self.name_filter {
3050 if macro_name != filter {
3051 syn::visit::visit_stmt(self, node);
3052 return;
3053 }
3054 }
3055
3056 let snippet = self.editor.format_stmt_macro(macro_stmt);
3058 let location = self.editor.span_to_location(macro_stmt.span());
3059
3060 let preceding_comment = if self.include_comments {
3062 extract_preceding_comment(&self.editor.content, location.line)
3063 } else {
3064 None
3065 };
3066
3067 self.results.push(InspectResult {
3068 file_path: String::new(), node_type: "macro-call".to_string(),
3070 identifier: macro_name,
3071 location,
3072 snippet,
3073 preceding_comment,
3074 });
3075 }
3076
3077 syn::visit::visit_stmt(self, node);
3079 }
3080 }
3081
3082 let mut visitor = MacroCallVisitor {
3083 results: &mut results,
3084 name_filter,
3085 editor: self,
3086 include_comments,
3087 };
3088
3089 for item in &self.syntax_tree.items {
3091 syn::visit::visit_item(&mut visitor, item);
3092 }
3093 }
3094 "struct" => {
3095 struct StructDefVisitor<'a> {
3097 results: &'a mut Vec<InspectResult>,
3098 name_filter: Option<&'a str>,
3099 editor: &'a RustEditor,
3100 include_comments: bool,
3101 }
3102
3103 impl<'ast, 'a> Visit<'ast> for StructDefVisitor<'a> {
3104 fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
3105 let struct_name = node.ident.to_string();
3106
3107 if let Some(filter) = self.name_filter {
3109 if struct_name != filter {
3110 syn::visit::visit_item_struct(self, node);
3111 return;
3112 }
3113 }
3114
3115 let snippet = self.editor.format_item_struct(node);
3117 let location = self.editor.span_to_location(node.span());
3118
3119 let preceding_comment = if self.include_comments {
3121 extract_preceding_comment(&self.editor.content, location.line)
3122 } else {
3123 None
3124 };
3125
3126 self.results.push(InspectResult {
3127 file_path: String::new(),
3128 node_type: "struct".to_string(),
3129 identifier: struct_name,
3130 location,
3131 snippet,
3132 preceding_comment,
3133 });
3134
3135 syn::visit::visit_item_struct(self, node);
3136 }
3137 }
3138
3139 let mut visitor = StructDefVisitor {
3140 results: &mut results,
3141 name_filter,
3142 editor: self,
3143 include_comments,
3144 };
3145
3146 for item in &self.syntax_tree.items {
3147 syn::visit::visit_item(&mut visitor, item);
3148 }
3149 }
3150 "enum" => {
3151 struct EnumDefVisitor<'a> {
3158 results: &'a mut Vec<InspectResult>,
3159 name_filter: Option<&'a str>,
3160 variant_filter: Option<&'a str>,
3161 editor: &'a RustEditor,
3162 include_comments: bool,
3163 }
3164
3165 impl<'ast, 'a> Visit<'ast> for EnumDefVisitor<'a> {
3166 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
3167 let enum_name = node.ident.to_string();
3168
3169 let (enum_name_filter, implicit_variant_filter) = if let Some(filter) = self.name_filter {
3171 if filter.contains("::") {
3172 let parts: Vec<&str> = filter.split("::").collect();
3174 if parts.len() == 2 {
3175 if parts[0] == "*" {
3176 (None, Some(parts[1]))
3178 } else {
3179 (Some(parts[0]), Some(parts[1]))
3181 }
3182 } else {
3183 (Some(filter), None)
3185 }
3186 } else {
3187 (Some(filter), None)
3189 }
3190 } else {
3191 (None, None)
3192 };
3193
3194 let effective_variant_filter = self.variant_filter.or(implicit_variant_filter);
3196
3197 if let Some(filter) = enum_name_filter {
3199 if enum_name != filter {
3200 syn::visit::visit_item_enum(self, node);
3201 return;
3202 }
3203 }
3204
3205 if let Some(variant_name) = effective_variant_filter {
3207 let has_variant = node.variants.iter().any(|v| v.ident.to_string() == variant_name);
3208 if !has_variant {
3209 syn::visit::visit_item_enum(self, node);
3210 return;
3211 }
3212 }
3213
3214 let snippet = if let Some(variant_name) = effective_variant_filter {
3216 self.editor.format_item_enum_variant_only(node, variant_name)
3218 } else {
3219 self.editor.format_item_enum(node)
3220 };
3221
3222 let location = self.editor.span_to_location(node.span());
3223
3224 let preceding_comment = if self.include_comments {
3226 extract_preceding_comment(&self.editor.content, location.line)
3227 } else {
3228 None
3229 };
3230
3231 self.results.push(InspectResult {
3232 file_path: String::new(),
3233 node_type: "enum".to_string(),
3234 identifier: enum_name,
3235 location,
3236 snippet,
3237 preceding_comment,
3238 });
3239
3240 syn::visit::visit_item_enum(self, node);
3241 }
3242 }
3243
3244 let mut visitor = EnumDefVisitor {
3245 results: &mut results,
3246 name_filter,
3247 variant_filter,
3248 editor: self,
3249 include_comments,
3250 };
3251
3252 for item in &self.syntax_tree.items {
3253 syn::visit::visit_item(&mut visitor, item);
3254 }
3255 }
3256 "function" => {
3257 struct FunctionDefVisitor<'a> {
3259 results: &'a mut Vec<InspectResult>,
3260 name_filter: Option<&'a str>,
3261 editor: &'a RustEditor,
3262 include_comments: bool,
3263 }
3264
3265 impl<'ast, 'a> Visit<'ast> for FunctionDefVisitor<'a> {
3266 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3267 let fn_name = node.sig.ident.to_string();
3268
3269 if let Some(filter) = self.name_filter {
3271 if fn_name != filter {
3272 syn::visit::visit_item_fn(self, node);
3273 return;
3274 }
3275 }
3276
3277 let snippet = self.editor.format_item_fn(node);
3279 let location = self.editor.span_to_location(node.span());
3280
3281 let preceding_comment = if self.include_comments {
3283 extract_preceding_comment(&self.editor.content, location.line)
3284 } else {
3285 None
3286 };
3287
3288 self.results.push(InspectResult {
3289 file_path: String::new(),
3290 node_type: "function".to_string(),
3291 identifier: fn_name,
3292 location,
3293 snippet,
3294 preceding_comment,
3295 });
3296
3297 syn::visit::visit_item_fn(self, node);
3298 }
3299 }
3300
3301 let mut visitor = FunctionDefVisitor {
3302 results: &mut results,
3303 name_filter,
3304 editor: self,
3305 include_comments,
3306 };
3307
3308 for item in &self.syntax_tree.items {
3309 syn::visit::visit_item(&mut visitor, item);
3310 }
3311 }
3312 "impl-method" => {
3313 struct ImplMethodVisitor<'a> {
3315 results: &'a mut Vec<InspectResult>,
3316 name_filter: Option<&'a str>,
3317 editor: &'a RustEditor,
3318 include_comments: bool,
3319 current_impl_type: Option<String>,
3320 }
3321
3322 impl<'ast, 'a> Visit<'ast> for ImplMethodVisitor<'a> {
3323 fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
3324 let impl_type = if let syn::Type::Path(type_path) = &*node.self_ty {
3326 type_path.path.segments.last()
3327 .map(|seg| seg.ident.to_string())
3328 } else {
3329 None
3330 };
3331
3332 let prev_impl_type = self.current_impl_type.clone();
3333 self.current_impl_type = impl_type;
3334
3335 syn::visit::visit_item_impl(self, node);
3336
3337 self.current_impl_type = prev_impl_type;
3338 }
3339
3340 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
3341 let method_name = node.sig.ident.to_string();
3342
3343 let identifier = if let Some(ref impl_type) = self.current_impl_type {
3345 format!("{}::{}", impl_type, method_name)
3346 } else {
3347 method_name.clone()
3348 };
3349
3350 if let Some(filter) = self.name_filter {
3352 if !identifier.contains(filter) && method_name != filter {
3354 syn::visit::visit_impl_item_fn(self, node);
3355 return;
3356 }
3357 }
3358
3359 let snippet = self.editor.format_impl_item_fn(node);
3361 let location = self.editor.span_to_location(node.span());
3362
3363 let preceding_comment = if self.include_comments {
3365 extract_preceding_comment(&self.editor.content, location.line)
3366 } else {
3367 None
3368 };
3369
3370 self.results.push(InspectResult {
3371 file_path: String::new(),
3372 node_type: "impl-method".to_string(),
3373 identifier,
3374 location,
3375 snippet,
3376 preceding_comment,
3377 });
3378
3379 syn::visit::visit_impl_item_fn(self, node);
3380 }
3381 }
3382
3383 let mut visitor = ImplMethodVisitor {
3384 results: &mut results,
3385 name_filter,
3386 editor: self,
3387 include_comments,
3388 current_impl_type: None,
3389 };
3390
3391 for item in &self.syntax_tree.items {
3392 syn::visit::visit_item(&mut visitor, item);
3393 }
3394 }
3395 "trait" => {
3396 struct TraitDefVisitor<'a> {
3398 results: &'a mut Vec<InspectResult>,
3399 name_filter: Option<&'a str>,
3400 editor: &'a RustEditor,
3401 include_comments: bool,
3402 }
3403
3404 impl<'ast, 'a> Visit<'ast> for TraitDefVisitor<'a> {
3405 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
3406 let trait_name = node.ident.to_string();
3407
3408 if let Some(filter) = self.name_filter {
3410 if trait_name != filter {
3411 syn::visit::visit_item_trait(self, node);
3412 return;
3413 }
3414 }
3415
3416 let snippet = self.editor.format_item_trait(node);
3418 let location = self.editor.span_to_location(node.span());
3419
3420 let preceding_comment = if self.include_comments {
3422 extract_preceding_comment(&self.editor.content, location.line)
3423 } else {
3424 None
3425 };
3426
3427 self.results.push(InspectResult {
3428 file_path: String::new(),
3429 node_type: "trait".to_string(),
3430 identifier: trait_name,
3431 location,
3432 snippet,
3433 preceding_comment,
3434 });
3435
3436 syn::visit::visit_item_trait(self, node);
3437 }
3438 }
3439
3440 let mut visitor = TraitDefVisitor {
3441 results: &mut results,
3442 name_filter,
3443 editor: self,
3444 include_comments,
3445 };
3446
3447 for item in &self.syntax_tree.items {
3448 syn::visit::visit_item(&mut visitor, item);
3449 }
3450 }
3451 "const" => {
3452 struct ConstDefVisitor<'a> {
3454 results: &'a mut Vec<InspectResult>,
3455 name_filter: Option<&'a str>,
3456 editor: &'a RustEditor,
3457 include_comments: bool,
3458 }
3459
3460 impl<'ast, 'a> Visit<'ast> for ConstDefVisitor<'a> {
3461 fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
3462 let const_name = node.ident.to_string();
3463
3464 if let Some(filter) = self.name_filter {
3466 if const_name != filter {
3467 syn::visit::visit_item_const(self, node);
3468 return;
3469 }
3470 }
3471
3472 let snippet = self.editor.format_item_const(node);
3474 let location = self.editor.span_to_location(node.span());
3475
3476 let preceding_comment = if self.include_comments {
3478 extract_preceding_comment(&self.editor.content, location.line)
3479 } else {
3480 None
3481 };
3482
3483 self.results.push(InspectResult {
3484 file_path: String::new(),
3485 node_type: "const".to_string(),
3486 identifier: const_name,
3487 location,
3488 snippet,
3489 preceding_comment,
3490 });
3491
3492 syn::visit::visit_item_const(self, node);
3493 }
3494 }
3495
3496 let mut visitor = ConstDefVisitor {
3497 results: &mut results,
3498 name_filter,
3499 editor: self,
3500 include_comments,
3501 };
3502
3503 for item in &self.syntax_tree.items {
3504 syn::visit::visit_item(&mut visitor, item);
3505 }
3506 }
3507 "static" => {
3508 struct StaticDefVisitor<'a> {
3510 results: &'a mut Vec<InspectResult>,
3511 name_filter: Option<&'a str>,
3512 editor: &'a RustEditor,
3513 include_comments: bool,
3514 }
3515
3516 impl<'ast, 'a> Visit<'ast> for StaticDefVisitor<'a> {
3517 fn visit_item_static(&mut self, node: &'ast syn::ItemStatic) {
3518 let static_name = node.ident.to_string();
3519
3520 if let Some(filter) = self.name_filter {
3522 if static_name != filter {
3523 syn::visit::visit_item_static(self, node);
3524 return;
3525 }
3526 }
3527
3528 let snippet = self.editor.format_item_static(node);
3530 let location = self.editor.span_to_location(node.span());
3531
3532 let preceding_comment = if self.include_comments {
3534 extract_preceding_comment(&self.editor.content, location.line)
3535 } else {
3536 None
3537 };
3538
3539 self.results.push(InspectResult {
3540 file_path: String::new(),
3541 node_type: "static".to_string(),
3542 identifier: static_name,
3543 location,
3544 snippet,
3545 preceding_comment,
3546 });
3547
3548 syn::visit::visit_item_static(self, node);
3549 }
3550 }
3551
3552 let mut visitor = StaticDefVisitor {
3553 results: &mut results,
3554 name_filter,
3555 editor: self,
3556 include_comments,
3557 };
3558
3559 for item in &self.syntax_tree.items {
3560 syn::visit::visit_item(&mut visitor, item);
3561 }
3562 }
3563 "type-alias" => {
3564 struct TypeAliasVisitor<'a> {
3566 results: &'a mut Vec<InspectResult>,
3567 name_filter: Option<&'a str>,
3568 editor: &'a RustEditor,
3569 include_comments: bool,
3570 }
3571
3572 impl<'ast, 'a> Visit<'ast> for TypeAliasVisitor<'a> {
3573 fn visit_item_type(&mut self, node: &'ast syn::ItemType) {
3574 let type_name = node.ident.to_string();
3575
3576 if let Some(filter) = self.name_filter {
3578 if type_name != filter {
3579 syn::visit::visit_item_type(self, node);
3580 return;
3581 }
3582 }
3583
3584 let snippet = self.editor.format_item_type(node);
3586 let location = self.editor.span_to_location(node.span());
3587
3588 let preceding_comment = if self.include_comments {
3590 extract_preceding_comment(&self.editor.content, location.line)
3591 } else {
3592 None
3593 };
3594
3595 self.results.push(InspectResult {
3596 file_path: String::new(),
3597 node_type: "type-alias".to_string(),
3598 identifier: type_name,
3599 location,
3600 snippet,
3601 preceding_comment,
3602 });
3603
3604 syn::visit::visit_item_type(self, node);
3605 }
3606 }
3607
3608 let mut visitor = TypeAliasVisitor {
3609 results: &mut results,
3610 name_filter,
3611 editor: self,
3612 include_comments,
3613 };
3614
3615 for item in &self.syntax_tree.items {
3616 syn::visit::visit_item(&mut visitor, item);
3617 }
3618 }
3619 "mod" => {
3620 struct ModDefVisitor<'a> {
3622 results: &'a mut Vec<InspectResult>,
3623 name_filter: Option<&'a str>,
3624 editor: &'a RustEditor,
3625 include_comments: bool,
3626 }
3627
3628 impl<'ast, 'a> Visit<'ast> for ModDefVisitor<'a> {
3629 fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
3630 let mod_name = node.ident.to_string();
3631
3632 if let Some(filter) = self.name_filter {
3634 if mod_name != filter {
3635 syn::visit::visit_item_mod(self, node);
3636 return;
3637 }
3638 }
3639
3640 let snippet = self.editor.format_item_mod(node);
3642 let location = self.editor.span_to_location(node.span());
3643
3644 let preceding_comment = if self.include_comments {
3646 extract_preceding_comment(&self.editor.content, location.line)
3647 } else {
3648 None
3649 };
3650
3651 self.results.push(InspectResult {
3652 file_path: String::new(),
3653 node_type: "mod".to_string(),
3654 identifier: mod_name,
3655 location,
3656 snippet,
3657 preceding_comment,
3658 });
3659
3660 syn::visit::visit_item_mod(self, node);
3661 }
3662 }
3663
3664 let mut visitor = ModDefVisitor {
3665 results: &mut results,
3666 name_filter,
3667 editor: self,
3668 include_comments,
3669 };
3670
3671 for item in &self.syntax_tree.items {
3672 syn::visit::visit_item(&mut visitor, item);
3673 }
3674 }
3675 _ => anyhow::bail!("Unsupported node type: {}", node_type),
3676 }
3677
3678 Ok(results)
3679 }
3680
3681 fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
3683 let start = self.span_to_byte_offset(expr.span().start());
3685 let end = self.span_to_byte_offset(expr.span().end());
3686
3687 let original = &self.content[start..end];
3689
3690 original.split_whitespace().collect::<Vec<_>>().join(" ")
3692 }
3693
3694 fn format_match_arm(&self, arm: &syn::Arm) -> String {
3696 let start = self.span_to_byte_offset(arm.span().start());
3698 let end = self.span_to_byte_offset(arm.span().end());
3699
3700 let original = &self.content[start..end];
3702
3703 original.split_whitespace().collect::<Vec<_>>().join(" ")
3705 }
3706
3707 fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
3709 let start = self.span_to_byte_offset(expr.span().start());
3711 let end = self.span_to_byte_offset(expr.span().end());
3712
3713 let original = &self.content[start..end];
3715
3716 original.split_whitespace().collect::<Vec<_>>().join(" ")
3718 }
3719
3720 fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
3722 let start = self.span_to_byte_offset(expr.span().start());
3724 let end = self.span_to_byte_offset(expr.span().end());
3725
3726 let original = &self.content[start..end];
3728
3729 original.split_whitespace().collect::<Vec<_>>().join(" ")
3731 }
3732
3733 fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
3735 let start = self.span_to_byte_offset(expr.span().start());
3737 let end = self.span_to_byte_offset(expr.span().end());
3738
3739 let original = &self.content[start..end];
3741
3742 original.split_whitespace().collect::<Vec<_>>().join(" ")
3744 }
3745
3746 fn format_ident(&self, ident: &syn::Ident) -> String {
3748 ident.to_string()
3749 }
3750
3751 fn format_type_path(&self, ty: &syn::TypePath) -> String {
3753 let start = self.span_to_byte_offset(ty.span().start());
3755 let end = self.span_to_byte_offset(ty.span().end());
3756
3757 let original = &self.content[start..end];
3759
3760 original.split_whitespace().collect::<Vec<_>>().join(" ")
3762 }
3763
3764 fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
3766 let start = self.span_to_byte_offset(expr.span().start());
3768 let end = self.span_to_byte_offset(expr.span().end());
3769
3770 let original = &self.content[start..end];
3772
3773 original.split_whitespace().collect::<Vec<_>>().join(" ")
3775 }
3776
3777 fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
3779 let start = self.span_to_byte_offset(stmt.span().start());
3781 let end = self.span_to_byte_offset(stmt.span().end());
3782
3783 let original = &self.content[start..end];
3785
3786 original.split_whitespace().collect::<Vec<_>>().join(" ")
3788 }
3789
3790 fn format_item_struct(&self, item: &syn::ItemStruct) -> String {
3792 let start = self.span_to_byte_offset(item.span().start());
3793 let end = self.span_to_byte_offset(item.span().end());
3794 let original = &self.content[start..end];
3795 original.to_string()
3796 }
3797
3798 fn format_item_enum(&self, item: &syn::ItemEnum) -> String {
3800 let start = self.span_to_byte_offset(item.span().start());
3801 let end = self.span_to_byte_offset(item.span().end());
3802 let original = &self.content[start..end];
3803 original.to_string()
3804 }
3805
3806 fn format_item_enum_variant_only(&self, item: &syn::ItemEnum, variant_name: &str) -> String {
3808 let variant = item.variants.iter()
3810 .find(|v| v.ident.to_string() == variant_name);
3811
3812 if let Some(variant) = variant {
3813 let enum_start = self.span_to_byte_offset(item.span().start());
3815 let variants_start = if !item.variants.is_empty() {
3816 self.span_to_byte_offset(item.variants.first().unwrap().span().start())
3817 } else {
3818 self.span_to_byte_offset(item.span().end())
3819 };
3820
3821 let header = &self.content[enum_start..variants_start].trim_end();
3823
3824 let variant_start = self.span_to_byte_offset(variant.span().start());
3826 let variant_end = self.span_to_byte_offset(variant.span().end());
3827 let variant_source = &self.content[variant_start..variant_end];
3828
3829 format!("{}\n {},\n // ... {} other variant{}\n}}",
3831 header,
3832 variant_source,
3833 item.variants.len() - 1,
3834 if item.variants.len() - 1 == 1 { "" } else { "s" }
3835 )
3836 } else {
3837 self.format_item_enum(item)
3839 }
3840 }
3841
3842 fn format_item_fn(&self, item: &syn::ItemFn) -> String {
3844 let start = self.span_to_byte_offset(item.span().start());
3845 let end = self.span_to_byte_offset(item.span().end());
3846 let original = &self.content[start..end];
3847 original.to_string()
3848 }
3849
3850 fn format_impl_item_fn(&self, item: &syn::ImplItemFn) -> String {
3852 let start = self.span_to_byte_offset(item.span().start());
3853 let end = self.span_to_byte_offset(item.span().end());
3854 let original = &self.content[start..end];
3855 original.to_string()
3856 }
3857
3858 fn format_trait_item_fn(&self, item: &syn::TraitItemFn) -> String {
3860 let start = self.span_to_byte_offset(item.span().start());
3861 let end = self.span_to_byte_offset(item.span().end());
3862 let original = &self.content[start..end];
3863 original.to_string()
3864 }
3865
3866 fn format_item_trait(&self, item: &syn::ItemTrait) -> String {
3868 let start = self.span_to_byte_offset(item.span().start());
3869 let end = self.span_to_byte_offset(item.span().end());
3870 let original = &self.content[start..end];
3871 original.to_string()
3872 }
3873
3874 fn format_item_const(&self, item: &syn::ItemConst) -> String {
3876 let start = self.span_to_byte_offset(item.span().start());
3877 let end = self.span_to_byte_offset(item.span().end());
3878 let original = &self.content[start..end];
3879 original.to_string()
3880 }
3881
3882 fn format_item_static(&self, item: &syn::ItemStatic) -> String {
3884 let start = self.span_to_byte_offset(item.span().start());
3885 let end = self.span_to_byte_offset(item.span().end());
3886 let original = &self.content[start..end];
3887 original.to_string()
3888 }
3889
3890 fn format_item_type(&self, item: &syn::ItemType) -> String {
3892 let start = self.span_to_byte_offset(item.span().start());
3893 let end = self.span_to_byte_offset(item.span().end());
3894 let original = &self.content[start..end];
3895 original.to_string()
3896 }
3897
3898 fn format_item_mod(&self, item: &syn::ItemMod) -> String {
3900 let start = self.span_to_byte_offset(item.span().start());
3901 let end = self.span_to_byte_offset(item.span().end());
3902 let original = &self.content[start..end];
3903 original.to_string()
3904 }
3905
3906 #[allow(dead_code)]
3908 pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
3909 for (index, item) in self.syntax_tree.items.iter().enumerate() {
3910 match (node_type, item) {
3911 ("struct", Item::Struct(s)) if s.ident == name => {
3912 return Ok(index);
3913 }
3914 ("enum", Item::Enum(e)) if e.ident == name => {
3915 return Ok(index);
3916 }
3917 ("fn", Item::Fn(f)) if f.sig.ident == name => {
3918 return Ok(index);
3919 }
3920 ("impl", Item::Impl(impl_block)) => {
3921 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
3923 if let Some(segment) = type_path.path.segments.last() {
3924 if segment.ident == name {
3925 return Ok(index);
3926 }
3927 }
3928 }
3929 }
3930 _ => {}
3931 }
3932 }
3933
3934 anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
3935 }
3936
3937 #[allow(dead_code)]
3939 pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
3940 if index >= self.syntax_tree.items.len() {
3941 anyhow::bail!("Index {} out of bounds", index);
3942 }
3943
3944 self.syntax_tree.items[index] = new_item;
3946
3947 self.content = prettyplease::unparse(&self.syntax_tree);
3949
3950 self.line_offsets = Self::compute_line_offsets(&self.content);
3952
3953 Ok(())
3954 }
3955
3956 fn span_to_location(&self, span: Span) -> NodeLocation {
3957 let start = span.start();
3958 let end = span.end();
3959
3960 NodeLocation {
3961 line: start.line,
3962 column: start.column,
3963 end_line: end.line,
3964 end_column: end.column,
3965 }
3966 }
3967
3968 pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
3970 use crate::operations::{InspectResult, TransformAction};
3971
3972 let matches = self.inspect(Some(&op.node_type), op.name_filter.as_deref(), None, false)?;
3974
3975 let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
3977 matches.into_iter()
3978 .filter(|m| m.snippet.contains(content_filter))
3979 .collect()
3980 } else {
3981 matches
3982 };
3983
3984 if filtered_matches.is_empty() {
3985 return Ok(ModificationResult {
3986 changed: false,
3987 modified_nodes: vec![],
3988 unmatched_qualified_paths: None,
3989 });
3990 }
3991
3992 let mut sorted_matches = filtered_matches;
3995 sorted_matches.sort_by(|a, b| {
3996 b.location.line.cmp(&a.location.line)
3997 .then(b.location.column.cmp(&a.location.column))
3998 });
3999
4000 let mut modified_nodes = Vec::new();
4001
4002 for match_result in &sorted_matches {
4003 let backup_node = BackupNode {
4005 node_type: match_result.node_type.clone(),
4006 identifier: match_result.identifier.clone(),
4007 original_content: match_result.snippet.clone(),
4008 location: match_result.location.clone(),
4009 };
4010
4011 let start_offset = self.line_column_to_byte_offset(
4013 match_result.location.line,
4014 match_result.location.column
4015 )?;
4016 let end_offset = self.line_column_to_byte_offset(
4017 match_result.location.end_line,
4018 match_result.location.end_column
4019 )?;
4020
4021 let original_text = &self.content[start_offset..end_offset];
4023
4024 let replacement = match &op.action {
4026 TransformAction::Comment => {
4027 format!("// {}", original_text.replace("\n", "\n// "))
4029 }
4030 TransformAction::Remove => {
4031 String::new()
4033 }
4034 TransformAction::Replace { with } => {
4035 with.clone()
4037 }
4038 };
4039
4040 self.content.replace_range(start_offset..end_offset, &replacement);
4042
4043 self.line_offsets = Self::compute_line_offsets(&self.content);
4045
4046 modified_nodes.push(backup_node);
4047 }
4048
4049 if !modified_nodes.is_empty() {
4051 }
4055
4056 Ok(ModificationResult {
4057 changed: !modified_nodes.is_empty(),
4058 modified_nodes,
4059 unmatched_qualified_paths: None,
4060 })
4061 }
4062
4063 pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
4065 use crate::operations::EditMode;
4066
4067 let path_resolver = if let Some(enum_path) = &op.enum_path {
4069 let mut resolver = PathResolver::new(enum_path)
4070 .ok_or_else(|| anyhow::anyhow!("Invalid enum path: {}", enum_path))?;
4071
4072 resolver.scan_file(&self.syntax_tree);
4074 Some(resolver)
4075 } else {
4076 None
4077 };
4078
4079 match op.edit_mode {
4080 EditMode::Surgical => {
4081 use syn::visit::Visit;
4083 use crate::surgical::Replacement;
4084
4085 let mut collector = EnumVariantReplacementCollector {
4086 enum_name: op.enum_name.clone(),
4087 old_variant: op.old_variant.clone(),
4088 new_variant: op.new_variant.clone(),
4089 path_resolver,
4090 replacements: Vec::new(),
4091 };
4092
4093 collector.visit_file(&self.syntax_tree);
4094
4095 if collector.replacements.is_empty() {
4096 return Ok(ModificationResult {
4097 changed: false,
4098 modified_nodes: vec![],
4099 unmatched_qualified_paths: None,
4100 });
4101 }
4102
4103 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
4105
4106 self.line_offsets = Self::compute_line_offsets(&self.content);
4108
4109 self.syntax_tree = syn::parse_str(&self.content)
4111 .context("Failed to re-parse after surgical edit")?;
4112
4113 let backup_node = BackupNode {
4114 node_type: "EnumVariantRename".to_string(),
4115 identifier: format!("{}::{} -> {} (surgical)", op.enum_name, op.old_variant, op.new_variant),
4116 original_content: format!("Renamed {} to {} in enum {} (surgical mode)", op.old_variant, op.new_variant, op.enum_name),
4117 location: NodeLocation {
4118 line: 1,
4119 column: 0,
4120 end_line: 1,
4121 end_column: 0,
4122 },
4123 };
4124
4125 Ok(ModificationResult {
4126 changed: true,
4127 modified_nodes: vec![backup_node],
4128 unmatched_qualified_paths: None,
4129 })
4130 }
4131 EditMode::Reformat => {
4132 let mut renamer = EnumVariantRenamer {
4134 enum_name: op.enum_name.clone(),
4135 old_variant: op.old_variant.clone(),
4136 new_variant: op.new_variant.clone(),
4137 path_resolver,
4138 modified: false,
4139 };
4140
4141 renamer.visit_file_mut(&mut self.syntax_tree);
4143
4144 if !renamer.modified {
4145 return Ok(ModificationResult {
4146 changed: false,
4147 modified_nodes: vec![],
4148 unmatched_qualified_paths: None,
4149 });
4150 }
4151
4152 self.content = prettyplease::unparse(&self.syntax_tree);
4154
4155 self.line_offsets = Self::compute_line_offsets(&self.content);
4157
4158 let backup_node = BackupNode {
4160 node_type: "EnumVariantRename".to_string(),
4161 identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
4162 original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
4163 location: NodeLocation {
4164 line: 1,
4165 column: 0,
4166 end_line: 1,
4167 end_column: 0,
4168 },
4169 };
4170
4171 Ok(ModificationResult {
4172 changed: true,
4173 modified_nodes: vec![backup_node],
4174 unmatched_qualified_paths: None,
4175 })
4176 }
4177 }
4178 }
4179
4180 pub(crate) fn rename_function(&mut self, op: &crate::operations::RenameFunctionOp) -> Result<ModificationResult> {
4182 use crate::operations::EditMode;
4183
4184 let path_resolver = if let Some(function_path) = &op.function_path {
4186 let mut resolver = PathResolver::new(function_path)
4187 .ok_or_else(|| anyhow::anyhow!("Invalid function path: {}", function_path))?;
4188
4189 resolver.scan_file(&self.syntax_tree);
4191 Some(resolver)
4192 } else {
4193 None
4194 };
4195
4196 match op.edit_mode {
4197 EditMode::Surgical => {
4198 use syn::visit::Visit;
4200
4201 let mut collector = FunctionReplacementCollector {
4202 old_name: op.old_name.clone(),
4203 new_name: op.new_name.clone(),
4204 path_resolver,
4205 replacements: Vec::new(),
4206 };
4207
4208 collector.visit_file(&self.syntax_tree);
4209
4210 if collector.replacements.is_empty() {
4211 return Ok(ModificationResult {
4212 changed: false,
4213 modified_nodes: vec![],
4214 unmatched_qualified_paths: None,
4215 });
4216 }
4217
4218 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
4220
4221 self.line_offsets = Self::compute_line_offsets(&self.content);
4223
4224 self.syntax_tree = syn::parse_str(&self.content)
4226 .context("Failed to re-parse after surgical edit")?;
4227
4228 let backup_node = BackupNode {
4229 node_type: "FunctionRename".to_string(),
4230 identifier: format!("{} -> {} (surgical)", op.old_name, op.new_name),
4231 original_content: format!("Renamed {} to {} (surgical mode)", op.old_name, op.new_name),
4232 location: NodeLocation {
4233 line: 1,
4234 column: 0,
4235 end_line: 1,
4236 end_column: 0,
4237 },
4238 };
4239
4240 Ok(ModificationResult {
4241 changed: true,
4242 modified_nodes: vec![backup_node],
4243 unmatched_qualified_paths: None,
4244 })
4245 }
4246 EditMode::Reformat => {
4247 let mut renamer = FunctionRenamer {
4249 old_name: op.old_name.clone(),
4250 new_name: op.new_name.clone(),
4251 path_resolver,
4252 modified: false,
4253 };
4254
4255 renamer.visit_file_mut(&mut self.syntax_tree);
4257
4258 if !renamer.modified {
4259 return Ok(ModificationResult {
4260 changed: false,
4261 modified_nodes: vec![],
4262 unmatched_qualified_paths: None,
4263 });
4264 }
4265
4266 self.content = prettyplease::unparse(&self.syntax_tree);
4268
4269 self.line_offsets = Self::compute_line_offsets(&self.content);
4271
4272 let backup_node = BackupNode {
4274 node_type: "FunctionRename".to_string(),
4275 identifier: format!("{} -> {}", op.old_name, op.new_name),
4276 original_content: format!("Renamed {} to {}", op.old_name, op.new_name),
4277 location: NodeLocation {
4278 line: 1,
4279 column: 0,
4280 end_line: 1,
4281 end_column: 0,
4282 },
4283 };
4284
4285 Ok(ModificationResult {
4286 changed: true,
4287 modified_nodes: vec![backup_node],
4288 unmatched_qualified_paths: None,
4289 })
4290 }
4291 }
4292 }
4293
4294 fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
4296 if line == 0 || line > self.line_offsets.len() {
4297 anyhow::bail!("Line {} out of range", line);
4298 }
4299
4300 let line_start = self.line_offsets[line - 1];
4301 Ok(line_start + column)
4302 }
4303}
4304
4305struct MatchArmAdder {
4307 target_function: Option<String>,
4308 arm_to_add: Arm,
4309 modified: bool,
4310 current_function: Option<String>,
4311 modified_function: Option<String>,
4312}
4313
4314impl VisitMut for MatchArmAdder {
4315 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4316 let prev_fn = self.current_function.clone();
4317 self.current_function = Some(node.sig.ident.to_string());
4318
4319 syn::visit_mut::visit_item_fn_mut(self, node);
4321
4322 self.current_function = prev_fn;
4323 }
4324
4325 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4326 if let Some(ref target) = self.target_function {
4328 if self.current_function.as_ref() != Some(target) {
4329 syn::visit_mut::visit_expr_match_mut(self, node);
4331 return;
4332 }
4333 }
4334
4335 let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
4337 let already_exists = node.arms.iter().any(|arm| {
4338 arm.pat.to_token_stream().to_string() == pattern_str
4339 });
4340
4341 if !already_exists {
4342 node.arms.push(self.arm_to_add.clone());
4344 self.modified = true;
4345 self.modified_function = self.current_function.clone();
4346 }
4347
4348 syn::visit_mut::visit_expr_match_mut(self, node);
4350 }
4351}
4352
4353struct MatchArmUpdater {
4355 target_function: Option<String>,
4356 pattern_to_match: String,
4357 new_body: syn::Expr,
4358 modified: bool,
4359 current_function: Option<String>,
4360 modified_function: Option<String>,
4361}
4362
4363impl VisitMut for MatchArmUpdater {
4364 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4365 let prev_fn = self.current_function.clone();
4366 self.current_function = Some(node.sig.ident.to_string());
4367
4368 syn::visit_mut::visit_item_fn_mut(self, node);
4369
4370 self.current_function = prev_fn;
4371 }
4372
4373 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4374 if let Some(ref target) = self.target_function {
4376 if self.current_function.as_ref() != Some(target) {
4377 syn::visit_mut::visit_expr_match_mut(self, node);
4378 return;
4379 }
4380 }
4381
4382 for arm in &mut node.arms {
4384 let pattern_str = arm.pat.to_token_stream().to_string();
4385 let pattern_normalized = pattern_str.replace(" ", "");
4387 let target_normalized = self.pattern_to_match.replace(" ", "");
4388
4389 if pattern_normalized == target_normalized {
4390 arm.body = Box::new(self.new_body.clone());
4391 self.modified = true;
4392 self.modified_function = self.current_function.clone();
4393 break;
4394 }
4395 }
4396
4397 syn::visit_mut::visit_expr_match_mut(self, node);
4398 }
4399}
4400
4401struct MatchArmRemover {
4403 target_function: Option<String>,
4404 pattern_to_remove: String,
4405 modified: bool,
4406 current_function: Option<String>,
4407 modified_function: Option<String>,
4408}
4409
4410impl VisitMut for MatchArmRemover {
4411 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4412 let prev_fn = self.current_function.clone();
4413 self.current_function = Some(node.sig.ident.to_string());
4414
4415 syn::visit_mut::visit_item_fn_mut(self, node);
4416
4417 self.current_function = prev_fn;
4418 }
4419
4420 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4421 if let Some(ref target) = self.target_function {
4423 if self.current_function.as_ref() != Some(target) {
4424 syn::visit_mut::visit_expr_match_mut(self, node);
4425 return;
4426 }
4427 }
4428
4429 let mut index_to_remove = None;
4431 for (i, arm) in node.arms.iter().enumerate() {
4432 let pattern_str = arm.pat.to_token_stream().to_string();
4433 let pattern_normalized = pattern_str.replace(" ", "");
4435 let target_normalized = self.pattern_to_remove.replace(" ", "");
4436
4437 if pattern_normalized == target_normalized {
4438 index_to_remove = Some(i);
4439 break;
4440 }
4441 }
4442
4443 if let Some(index) = index_to_remove {
4444 node.arms.remove(index);
4445 self.modified = true;
4446 self.modified_function = self.current_function.clone();
4447 }
4448
4449 syn::visit_mut::visit_expr_match_mut(self, node);
4450 }
4451}
4452
4453struct MultiMatchArmAdder {
4455 target_function: Option<String>,
4456 arms_to_add: Vec<(String, Arm)>, modified: bool,
4458 current_function: Option<String>,
4459 modified_function: Option<String>,
4460}
4461
4462impl VisitMut for MultiMatchArmAdder {
4463 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4464 let prev_fn = self.current_function.clone();
4465 self.current_function = Some(node.sig.ident.to_string());
4466
4467 syn::visit_mut::visit_item_fn_mut(self, node);
4468
4469 self.current_function = prev_fn;
4470 }
4471
4472 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4473 if let Some(ref target) = self.target_function {
4475 if self.current_function.as_ref() != Some(target) {
4476 syn::visit_mut::visit_expr_match_mut(self, node);
4477 return;
4478 }
4479 }
4480
4481 for (pattern_str, arm) in &self.arms_to_add {
4483 let already_exists = node.arms.iter().any(|existing_arm| {
4485 existing_arm.pat.to_token_stream().to_string() == *pattern_str
4486 });
4487
4488 if !already_exists {
4489 node.arms.push(arm.clone());
4490 self.modified = true;
4491 self.modified_function = self.current_function.clone();
4492 }
4493 }
4494
4495 syn::visit_mut::visit_expr_match_mut(self, node);
4496 }
4497}
4498
4499struct StructLiteralFieldAdder {
4501 struct_name: String,
4502 field_def: String,
4503 field_name: String,
4504 position: InsertPosition,
4505 path_resolver: Option<PathResolver>,
4506 modified: bool,
4507}
4508
4509impl VisitMut for StructLiteralFieldAdder {
4510 fn visit_expr_mut(&mut self, node: &mut Expr) {
4511 if let Expr::Struct(expr_struct) = node {
4513 let is_match = if let Some(resolver) = &self.path_resolver {
4514 resolver.matches_target(&expr_struct.path)
4516 } else {
4517 if self.struct_name.contains("::") {
4523 if self.struct_name.starts_with("*::") {
4525 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
4528 .map(|seg| seg.ident.to_string() == target_name)
4529 .unwrap_or(false)
4530 } else {
4531 let path_str = expr_struct.path.segments.iter()
4533 .map(|seg| seg.ident.to_string())
4534 .collect::<Vec<_>>()
4535 .join("::");
4536 path_str == self.struct_name
4537 }
4538 } else {
4539 expr_struct.path.segments.len() == 1
4541 && expr_struct.path.segments.last()
4542 .map(|seg| seg.ident.to_string())
4543 .as_ref() == Some(&self.struct_name)
4544 }
4545 };
4546
4547 if is_match {
4548 let field_exists = expr_struct.fields.iter().any(|fv| {
4550 fv.member.to_token_stream().to_string() == self.field_name
4551 });
4552
4553 if !field_exists {
4554 let field_value_code = format!("{{ {} }}", self.field_def);
4557 if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
4558 if let Some(new_fv) = expr.fields.first() {
4559 match &self.position {
4561 InsertPosition::First => {
4562 expr_struct.fields.insert(0, new_fv.clone());
4563 self.modified = true;
4564 }
4565 InsertPosition::Last => {
4566 expr_struct.fields.push(new_fv.clone());
4567 self.modified = true;
4568 }
4569 InsertPosition::After(after_field) => {
4570 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4572 fv.member.to_token_stream().to_string() == *after_field
4573 }) {
4574 expr_struct.fields.insert(pos + 1, new_fv.clone());
4575 self.modified = true;
4576 }
4577 }
4578 InsertPosition::Before(before_field) => {
4579 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4581 fv.member.to_token_stream().to_string() == *before_field
4582 }) {
4583 expr_struct.fields.insert(pos, new_fv.clone());
4584 self.modified = true;
4585 }
4586 }
4587 }
4588 }
4589 }
4590 }
4591 }
4592 }
4593
4594 syn::visit_mut::visit_expr_mut(self, node);
4597 }
4598}
4599
4600struct EnumVariantRenamer {
4602 enum_name: String,
4603 old_variant: String,
4604 new_variant: String,
4605 path_resolver: Option<PathResolver>,
4606 modified: bool,
4607}
4608
4609impl EnumVariantRenamer {
4610 fn rename_path(&mut self, path: &mut syn::Path) {
4620 let segments: Vec<_> = path.segments.iter().collect();
4622 let len = segments.len();
4623
4624 if len >= 2 {
4625 let potential_variant = &segments[len - 1];
4627 let potential_enum = &segments[len - 2];
4628
4629 if potential_enum.ident == self.enum_name
4630 && potential_variant.ident == self.old_variant
4631 {
4632 if let Some(resolver) = &self.path_resolver {
4636 let enum_path = syn::Path {
4638 leading_colon: path.leading_colon,
4639 segments: path.segments.iter()
4640 .take(len - 1)
4641 .cloned()
4642 .collect(),
4643 };
4644
4645 if resolver.matches_target(&enum_path) {
4647 path.segments[len - 1].ident = syn::Ident::new(
4648 &self.new_variant,
4649 path.segments[len - 1].ident.span()
4650 );
4651 self.modified = true;
4652 }
4653 } else {
4654 if len == 2 {
4657 path.segments[1].ident = syn::Ident::new(
4658 &self.new_variant,
4659 path.segments[1].ident.span()
4660 );
4661 self.modified = true;
4662 }
4663 }
4664 }
4665 } else if len == 1 {
4666 if segments[0].ident == self.old_variant {
4668 if self.path_resolver.is_none() {
4672 path.segments[0].ident = syn::Ident::new(
4673 &self.new_variant,
4674 path.segments[0].ident.span()
4675 );
4676 self.modified = true;
4677 }
4678 }
4679 }
4680 }
4681}
4682
4683impl VisitMut for EnumVariantRenamer {
4684 fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
4686 if node.ident == self.enum_name {
4687 for variant in &mut node.variants {
4688 if variant.ident == self.old_variant {
4689 variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
4690 self.modified = true;
4691 }
4692 }
4693 }
4694
4695 syn::visit_mut::visit_item_enum_mut(self, node);
4697 }
4698
4699 fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
4701 match pat {
4702 syn::Pat::TupleStruct(tuple_struct) => {
4703 self.rename_path(&mut tuple_struct.path);
4704 }
4705 syn::Pat::Struct(struct_pat) => {
4706 self.rename_path(&mut struct_pat.path);
4707 }
4708 syn::Pat::Path(path_pat) => {
4709 self.rename_path(&mut path_pat.path);
4710 }
4711 _ => {}
4712 }
4713
4714 syn::visit_mut::visit_pat_mut(self, pat);
4716 }
4717
4718 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4720 match expr {
4721 syn::Expr::Path(expr_path) => {
4722 self.rename_path(&mut expr_path.path);
4723 }
4724 syn::Expr::Call(call) => {
4725 if let syn::Expr::Path(path) = &mut *call.func {
4726 self.rename_path(&mut path.path);
4727 }
4728 }
4729 syn::Expr::Struct(struct_expr) => {
4730 self.rename_path(&mut struct_expr.path);
4731 }
4732 _ => {}
4733 }
4734
4735 syn::visit_mut::visit_expr_mut(self, expr);
4737 }
4738}
4739
4740struct EnumVariantReplacementCollector {
4742 enum_name: String,
4743 old_variant: String,
4744 new_variant: String,
4745 path_resolver: Option<PathResolver>,
4746 replacements: Vec<crate::surgical::Replacement>,
4747}
4748
4749impl EnumVariantReplacementCollector {
4750 fn collect_path_replacement(&mut self, path: &syn::Path) {
4752 let segments: Vec<_> = path.segments.iter().collect();
4753 let len = segments.len();
4754
4755 if len >= 2 {
4756 let potential_variant = &segments[len - 1];
4757 let potential_enum = &segments[len - 2];
4758
4759 if potential_enum.ident == self.enum_name
4760 && potential_variant.ident == self.old_variant
4761 {
4762 let should_rename = if let Some(resolver) = &self.path_resolver {
4766 let enum_path = syn::Path {
4767 leading_colon: path.leading_colon,
4768 segments: path.segments.iter()
4769 .take(len - 1)
4770 .cloned()
4771 .collect(),
4772 };
4773 resolver.matches_target(&enum_path)
4774 } else {
4775 len == 2
4777 };
4778
4779 if should_rename {
4780 let span = potential_variant.ident.span();
4781 let start = span.start();
4782 let end = span.end();
4783
4784 self.replacements.push(crate::surgical::Replacement::new(
4785 start,
4786 end,
4787 self.new_variant.clone(),
4788 ));
4789 }
4790 }
4791 } else if len == 1 && self.path_resolver.is_none() {
4792 if segments[0].ident == self.old_variant {
4794 let span = segments[0].ident.span();
4795 let start = span.start();
4796 let end = span.end();
4797
4798 self.replacements.push(crate::surgical::Replacement::new(
4799 start,
4800 end,
4801 self.new_variant.clone(),
4802 ));
4803 }
4804 }
4805 }
4806}
4807
4808impl<'ast> syn::visit::Visit<'ast> for EnumVariantReplacementCollector {
4809 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
4810 if node.ident == self.enum_name {
4811 for variant in &node.variants {
4812 if variant.ident == self.old_variant {
4813 let span = variant.ident.span();
4814 let start = span.start();
4815 let end = span.end();
4816
4817 self.replacements.push(crate::surgical::Replacement::new(
4818 start,
4819 end,
4820 self.new_variant.clone(),
4821 ));
4822 }
4823 }
4824 }
4825 syn::visit::visit_item_enum(self, node);
4826 }
4827
4828 fn visit_pat(&mut self, pat: &'ast syn::Pat) {
4829 match pat {
4830 syn::Pat::TupleStruct(tuple_struct) => {
4831 self.collect_path_replacement(&tuple_struct.path);
4832 }
4833 syn::Pat::Struct(struct_pat) => {
4834 self.collect_path_replacement(&struct_pat.path);
4835 }
4836 syn::Pat::Path(path_pat) => {
4837 self.collect_path_replacement(&path_pat.path);
4838 }
4839 _ => {}
4840 }
4841 syn::visit::visit_pat(self, pat);
4842 }
4843
4844 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4845 match expr {
4846 syn::Expr::Path(expr_path) => {
4847 self.collect_path_replacement(&expr_path.path);
4848 }
4849 syn::Expr::Call(call) => {
4850 if let syn::Expr::Path(path) = &*call.func {
4851 self.collect_path_replacement(&path.path);
4852 }
4853 }
4854 syn::Expr::Struct(struct_expr) => {
4855 self.collect_path_replacement(&struct_expr.path);
4856 }
4857 _ => {}
4858 }
4859 syn::visit::visit_expr(self, expr);
4860 }
4861}
4862
4863struct FunctionRenamer {
4865 old_name: String,
4866 new_name: String,
4867 path_resolver: Option<PathResolver>,
4868 modified: bool,
4869}
4870
4871impl FunctionRenamer {
4872 fn rename_ident(&mut self, ident: &mut syn::Ident) {
4874 if ident == &self.old_name {
4875 *ident = syn::Ident::new(&self.new_name, ident.span());
4876 self.modified = true;
4877 }
4878 }
4879
4880 fn matches_target_function(&self, path: &syn::Path) -> bool {
4882 if let Some(resolver) = &self.path_resolver {
4883 resolver.matches_target(path)
4884 } else {
4885 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4887 }
4888 }
4889}
4890
4891impl VisitMut for FunctionRenamer {
4892 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4893 self.rename_ident(&mut node.sig.ident);
4895 syn::visit_mut::visit_item_fn_mut(self, node);
4896 }
4897
4898 fn visit_impl_item_fn_mut(&mut self, node: &mut syn::ImplItemFn) {
4899 self.rename_ident(&mut node.sig.ident);
4901 syn::visit_mut::visit_impl_item_fn_mut(self, node);
4902 }
4903
4904 fn visit_trait_item_fn_mut(&mut self, node: &mut syn::TraitItemFn) {
4905 self.rename_ident(&mut node.sig.ident);
4907 syn::visit_mut::visit_trait_item_fn_mut(self, node);
4908 }
4909
4910 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4911 match expr {
4912 syn::Expr::Call(call) => {
4913 if let syn::Expr::Path(expr_path) = &mut *call.func {
4915 if self.matches_target_function(&expr_path.path) {
4916 if let Some(last_seg) = expr_path.path.segments.last_mut() {
4917 self.rename_ident(&mut last_seg.ident);
4918 }
4919 }
4920 }
4921 }
4922 syn::Expr::Path(expr_path) => {
4923 if self.matches_target_function(&expr_path.path) {
4925 if let Some(last_seg) = expr_path.path.segments.last_mut() {
4926 self.rename_ident(&mut last_seg.ident);
4927 }
4928 }
4929 }
4930 _ => {}
4931 }
4932 syn::visit_mut::visit_expr_mut(self, expr);
4933 }
4934}
4935
4936struct FunctionReplacementCollector {
4938 old_name: String,
4939 new_name: String,
4940 path_resolver: Option<PathResolver>,
4941 replacements: Vec<crate::surgical::Replacement>,
4942}
4943
4944impl FunctionReplacementCollector {
4945 fn collect_replacement(&mut self, ident: &syn::Ident) {
4947 if ident == &self.old_name {
4948 let span = ident.span();
4949 let start = span.start();
4950 let end = span.end();
4951
4952 self.replacements.push(crate::surgical::Replacement::new(
4953 start,
4954 end,
4955 self.new_name.clone(),
4956 ));
4957 }
4958 }
4959
4960 fn matches_target_function(&self, path: &syn::Path) -> bool {
4962 if let Some(resolver) = &self.path_resolver {
4963 resolver.matches_target(path)
4964 } else {
4965 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4967 }
4968 }
4969}
4970
4971impl<'ast> syn::visit::Visit<'ast> for FunctionReplacementCollector {
4972 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
4973 self.collect_replacement(&node.sig.ident);
4975 syn::visit::visit_item_fn(self, node);
4976 }
4977
4978 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
4979 self.collect_replacement(&node.sig.ident);
4981 syn::visit::visit_impl_item_fn(self, node);
4982 }
4983
4984 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
4985 self.collect_replacement(&node.sig.ident);
4987 syn::visit::visit_trait_item_fn(self, node);
4988 }
4989
4990 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4991 match expr {
4992 syn::Expr::Call(call) => {
4993 if let syn::Expr::Path(expr_path) = &*call.func {
4995 if self.matches_target_function(&expr_path.path) {
4996 if let Some(last_seg) = expr_path.path.segments.last() {
4997 self.collect_replacement(&last_seg.ident);
4998 }
4999 }
5000 }
5001 for arg in &call.args {
5003 syn::visit::visit_expr(self, arg);
5004 }
5005 return;
5007 }
5008 syn::Expr::Path(expr_path) => {
5009 if self.matches_target_function(&expr_path.path) {
5011 if let Some(last_seg) = expr_path.path.segments.last() {
5012 self.collect_replacement(&last_seg.ident);
5013 }
5014 }
5015 }
5016 _ => {}
5017 }
5018 syn::visit::visit_expr(self, expr);
5019 }
5020}
5021
5022fn generate_doc_comment(text: &str, style: &DocCommentStyle) -> String {
5028 match style {
5029 DocCommentStyle::Line => {
5030 text.lines()
5032 .map(|line| {
5033 if line.trim().is_empty() {
5034 "///".to_string()
5035 } else {
5036 format!("/// {}", line)
5037 }
5038 })
5039 .collect::<Vec<_>>()
5040 .join("\n")
5041 }
5042 DocCommentStyle::Block => {
5043 if text.contains('\n') {
5045 let lines = text.lines()
5047 .map(|line| format!(" * {}", line))
5048 .collect::<Vec<_>>()
5049 .join("\n");
5050 format!("/**\n{}\n */", lines)
5051 } else {
5052 format!("/** {} */", text)
5054 }
5055 }
5056 }
5057}
5058
5059fn extract_preceding_comment(content: &str, start_line: usize) -> Option<String> {
5062 if start_line == 0 {
5063 return None;
5064 }
5065
5066 let lines: Vec<&str> = content.lines().collect();
5067 if start_line > lines.len() {
5068 return None;
5069 }
5070
5071 let line_idx = start_line.saturating_sub(1); let mut comment_start = line_idx;
5075 let mut found_any_comment = false;
5076
5077 while comment_start > 0 {
5078 let prev_line = lines[comment_start - 1].trim();
5079
5080 let is_comment = prev_line.starts_with("///")
5082 || prev_line.starts_with("//!")
5083 || prev_line.starts_with("//")
5084 || prev_line.starts_with("/**")
5085 || prev_line.starts_with("/*!")
5086 || prev_line.starts_with("/*")
5087 || (prev_line.starts_with("*") && !prev_line.starts_with("*/"))
5088 || prev_line == "*/";
5089
5090 if is_comment {
5091 comment_start -= 1;
5092 found_any_comment = true;
5093 } else if prev_line.is_empty() && found_any_comment {
5094 comment_start -= 1;
5097 } else {
5098 break;
5099 }
5100 }
5101
5102 if !found_any_comment {
5103 return None;
5104 }
5105
5106 let comment_lines: Vec<String> = lines[comment_start..line_idx]
5108 .iter()
5109 .map(|&line| line.to_string())
5110 .collect();
5111
5112 if comment_lines.is_empty() {
5113 None
5114 } else {
5115 Some(comment_lines.join("\n"))
5116 }
5117}
5118
5119struct TargetFinder {
5121 target_type: String,
5122 target_name: String,
5123 found_position: Option<(usize, String)>, }
5125
5126impl TargetFinder {
5127 fn new(target_type: String, target_name: String) -> Self {
5128 Self {
5129 target_type,
5130 target_name,
5131 found_position: None,
5132 }
5133 }
5134}
5135
5136impl<'ast> syn::visit::Visit<'ast> for TargetFinder {
5137 fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
5138 if self.target_type == "struct" && node.ident.to_string() == self.target_name {
5139 let line = node.struct_token.span.start().line;
5142 self.found_position = Some((line, String::new()));
5143 }
5144 syn::visit::visit_item_struct(self, node);
5145 }
5146
5147 fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
5148 if self.target_type == "enum" && node.ident.to_string() == self.target_name {
5149 let line = node.enum_token.span.start().line;
5151 self.found_position = Some((line, String::new()));
5152 }
5153 syn::visit::visit_item_enum(self, node);
5154 }
5155
5156 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
5157 if self.target_type == "function" && node.sig.ident.to_string() == self.target_name {
5158 let line = node.sig.fn_token.span.start().line;
5160 self.found_position = Some((line, String::new()));
5161 }
5162 syn::visit::visit_item_fn(self, node);
5163 }
5164}
5165
5166impl RustEditor {
5167 pub fn add_doc_comment_surgical(
5169 &mut self,
5170 target_type: &str,
5171 target_name: &str,
5172 doc_text: &str,
5173 style: &DocCommentStyle,
5174 ) -> Result<ModificationResult> {
5175 use syn::visit::Visit;
5176
5177 let mut finder = TargetFinder::new(
5179 target_type.to_string(),
5180 target_name.to_string(),
5181 );
5182 finder.visit_file(&self.syntax_tree);
5183
5184 if let Some((line_num, _indent)) = finder.found_position {
5185 let line_idx = line_num.saturating_sub(1);
5187
5188 let comment = generate_doc_comment(doc_text, style);
5190
5191 let lines: Vec<&str> = self.content.lines().collect();
5193 if line_idx >= lines.len() {
5194 anyhow::bail!("Target not found at line {}", line_num);
5195 }
5196
5197 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5199
5200 let indent = target_line
5202 .chars()
5203 .take_while(|c| c.is_whitespace())
5204 .collect::<String>();
5205
5206 let mut new_lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
5208
5209 let comment_lines: Vec<String> = comment
5211 .lines()
5212 .map(|line| format!("{}{}", indent, line))
5213 .collect();
5214
5215 for (i, comment_line) in comment_lines.iter().rev().enumerate() {
5217 new_lines.insert(line_idx, comment_line.clone());
5218 }
5219
5220 self.content = new_lines.join("\n");
5222
5223 self.syntax_tree = syn::parse_str(&self.content)
5225 .context("Failed to re-parse after adding comment")?;
5226
5227 Ok(ModificationResult {
5228 changed: true,
5229 modified_nodes: vec![BackupNode {
5230 node_type: target_type.to_string(),
5231 identifier: target_name.to_string(),
5232 original_content: target_line,
5233 location: NodeLocation {
5234 line: line_num,
5235 column: 1,
5236 end_line: line_num,
5237 end_column: target_line_len,
5238 },
5239 }],
5240 unmatched_qualified_paths: None,
5241 })
5242 } else {
5243 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5244 }
5245 }
5246
5247 pub fn update_doc_comment_surgical(
5249 &mut self,
5250 target_type: &str,
5251 target_name: &str,
5252 doc_text: &str,
5253 style: &DocCommentStyle,
5254 ) -> Result<ModificationResult> {
5255 use syn::visit::Visit;
5256
5257 let mut finder = TargetFinder::new(
5259 target_type.to_string(),
5260 target_name.to_string(),
5261 );
5262 finder.visit_file(&self.syntax_tree);
5263
5264 if let Some((line_num, _indent)) = finder.found_position {
5265 let line_idx = line_num.saturating_sub(1);
5267
5268 let lines: Vec<&str> = self.content.lines().collect();
5270 if line_idx >= lines.len() {
5271 anyhow::bail!("Target not found at line {}", line_num);
5272 }
5273
5274 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5276
5277 let indent = target_line
5279 .chars()
5280 .take_while(|c| c.is_whitespace())
5281 .collect::<String>();
5282
5283 let mut doc_comment_start = line_idx;
5285 while doc_comment_start > 0 {
5286 let prev_line = lines[doc_comment_start - 1].trim();
5287 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5288 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5289 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5290 prev_line == "*/" {
5291 doc_comment_start -= 1;
5292 } else {
5293 break;
5294 }
5295 }
5296
5297 let mut new_lines: Vec<String> = Vec::new();
5299
5300 for i in 0..doc_comment_start {
5302 new_lines.push(lines[i].to_string());
5303 }
5304
5305 let comment = generate_doc_comment(doc_text, style);
5307 let comment_lines: Vec<String> = comment
5308 .lines()
5309 .map(|line| format!("{}{}", indent, line))
5310 .collect();
5311
5312 for comment_line in comment_lines {
5313 new_lines.push(comment_line);
5314 }
5315
5316 for i in line_idx..lines.len() {
5318 new_lines.push(lines[i].to_string());
5319 }
5320
5321 self.content = new_lines.join("\n");
5323
5324 self.syntax_tree = syn::parse_str(&self.content)
5326 .context("Failed to re-parse after updating comment")?;
5327
5328 Ok(ModificationResult {
5329 changed: true,
5330 modified_nodes: vec![BackupNode {
5331 node_type: target_type.to_string(),
5332 identifier: target_name.to_string(),
5333 original_content: target_line,
5334 location: NodeLocation {
5335 line: line_num,
5336 column: 1,
5337 end_line: line_num,
5338 end_column: target_line_len,
5339 },
5340 }],
5341 unmatched_qualified_paths: None,
5342 })
5343 } else {
5344 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5345 }
5346 }
5347
5348 pub fn remove_doc_comment_surgical(
5350 &mut self,
5351 target_type: &str,
5352 target_name: &str,
5353 ) -> Result<ModificationResult> {
5354 use syn::visit::Visit;
5355
5356 let mut finder = TargetFinder::new(
5358 target_type.to_string(),
5359 target_name.to_string(),
5360 );
5361 finder.visit_file(&self.syntax_tree);
5362
5363 if let Some((line_num, _indent)) = finder.found_position {
5364 let line_idx = line_num.saturating_sub(1);
5366
5367 let lines: Vec<&str> = self.content.lines().collect();
5369 if line_idx >= lines.len() {
5370 anyhow::bail!("Target not found at line {}", line_num);
5371 }
5372
5373 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5375
5376 let mut doc_comment_start = line_idx;
5378 while doc_comment_start > 0 {
5379 let prev_line = lines[doc_comment_start - 1].trim();
5380 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5381 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5382 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5383 prev_line == "*/" {
5384 doc_comment_start -= 1;
5385 } else {
5386 break;
5387 }
5388 }
5389
5390 let mut new_lines: Vec<String> = Vec::new();
5392
5393 for i in 0..doc_comment_start {
5395 new_lines.push(lines[i].to_string());
5396 }
5397
5398 for i in line_idx..lines.len() {
5402 new_lines.push(lines[i].to_string());
5403 }
5404
5405 self.content = new_lines.join("\n");
5407
5408 self.syntax_tree = syn::parse_str(&self.content)
5410 .context("Failed to re-parse after removing comment")?;
5411
5412 Ok(ModificationResult {
5413 changed: true,
5414 modified_nodes: vec![BackupNode {
5415 node_type: target_type.to_string(),
5416 identifier: target_name.to_string(),
5417 original_content: target_line,
5418 location: NodeLocation {
5419 line: line_num,
5420 column: 1,
5421 end_line: line_num,
5422 end_column: target_line_len,
5423 },
5424 }],
5425 unmatched_qualified_paths: None,
5426 })
5427 } else {
5428 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5429 }
5430 }
5431}