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 if is_enum_variant {
131 let final_field_def = if let Some(literal_default) = &op.literal_default {
138 let field_name = op.field_def.split(':')
140 .next()
141 .map(|s| s.trim().to_string())
142 .context("Failed to extract field name")?;
143 format!("{}: {}", field_name, literal_default)
144 } else if op.field_def.contains(':') {
145 op.field_def.clone()
147 } else {
148 anyhow::bail!(
149 "For enum variant literals, field definition must include a value.\n\
150 Either use: --field \"layer: None\" or --field \"layer\" --literal-default \"None\""
151 );
152 };
153
154 let literal_op = AddStructLiteralFieldOp {
156 struct_name: op.struct_name.clone(),
157 field_def: final_field_def,
158 position: op.position.clone(),
159 struct_path: None,
160 };
161
162 let literal_result = self.add_struct_literal_field(&literal_op)
164 .context("Failed to update struct literals")?;
165
166 return Ok(literal_result);
167 }
168
169 let item_struct = self.syntax_tree.items.iter()
171 .find_map(|item| {
172 if let Item::Struct(s) = item {
173 if s.ident == op.struct_name {
174 return Some(s.clone());
175 }
176 }
177 None
178 })
179 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
180
181 if let Some(ref where_filter) = op.where_filter {
183 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
184 return Ok(ModificationResult {
186 changed: false,
187 modified_nodes: vec![],
188 });
189 }
190 }
191
192 if op.literal_default.is_none() {
194 let backup_node = BackupNode {
196 node_type: "struct".to_string(),
197 identifier: op.struct_name.clone(),
198 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
199 location: self.span_to_location(item_struct.span()),
200 };
201
202 let modified = self.insert_struct_field(&item_struct, op)
204 .context("Failed to add field to struct definition")?;
205
206 if !modified {
207 return Ok(ModificationResult {
208 changed: false,
209 modified_nodes: vec![],
210 });
211 }
212
213 return Ok(ModificationResult {
214 changed: true,
215 modified_nodes: vec![backup_node],
216 });
217 }
218
219 let literal_default = op.literal_default.as_ref().unwrap();
223
224 let has_type = op.field_def.contains(':');
227
228 let mut def_modified = false;
229 if has_type {
230 let backup_node = BackupNode {
232 node_type: "struct".to_string(),
233 identifier: op.struct_name.clone(),
234 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
235 location: self.span_to_location(item_struct.span()),
236 };
237
238 def_modified = self.insert_struct_field(&item_struct, op)
240 .context("Failed to add field to struct definition")?;
241
242 if def_modified {
243 modified_nodes.push(backup_node);
244 self.syntax_tree = syn::parse_str(&self.content)
246 .context("Failed to re-parse content after adding struct field")?;
247 self.line_offsets = Self::compute_line_offsets(&self.content);
248 }
249 }
250
251 let field_name = op.field_def.split(':')
254 .next()
255 .map(|s| s.trim().to_string())
256 .context("Failed to extract field name from field definition")?;
257
258 let literal_op = AddStructLiteralFieldOp {
260 struct_name: op.struct_name.clone(),
261 field_def: format!("{}: {}", field_name, literal_default),
262 position: op.position.clone(),
263 struct_path: None, };
265
266 let literal_result = self.add_struct_literal_field(&literal_op)
268 .context("Failed to update struct literals")?;
269 modified_nodes.extend(literal_result.modified_nodes);
270
271 Ok(ModificationResult {
272 changed: true,
273 modified_nodes,
274 })
275 }
276
277 fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
278 if let Fields::Named(ref fields) = item_struct.fields {
279 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
281 let dummy: ItemStruct = parse_str(&field_code)
282 .context("Failed to parse field definition")?;
283
284 let new_field = if let Fields::Named(ref nf) = dummy.fields {
285 nf.named.first()
286 .context("No field found in definition")?
287 .clone()
288 } else {
289 anyhow::bail!("Expected named field");
290 };
291
292 let new_field_name = new_field.ident.as_ref()
294 .map(|i| i.to_string())
295 .context("Field must have a name")?;
296
297 if fields.named.iter().any(|f| {
298 f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
299 }) {
300 return Ok(false);
302 }
303
304 let insert_pos = match &op.position {
306 InsertPosition::First => {
307 if let Some(first_field) = fields.named.first() {
308 self.span_to_byte_offset(first_field.span().start())
309 } else {
310 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
312 brace_pos + 1
313 }
314 }
315 InsertPosition::Last => {
316 if let Some(last_field) = fields.named.last() {
317 let end = self.span_to_byte_offset(last_field.span().end());
318 self.find_after_field_end(end)
320 } else {
321 let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
323 brace_pos + 1
324 }
325 }
326 InsertPosition::After(name) => {
327 let field = fields.named.iter()
328 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
329 .with_context(|| format!("Field '{}' not found", name))?;
330 let end = self.span_to_byte_offset(field.span().end());
331 self.find_after_field_end(end)
332 }
333 InsertPosition::Before(name) => {
334 let field = fields.named.iter()
335 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
336 .with_context(|| format!("Field '{}' not found", name))?;
337 self.span_to_byte_offset(field.span().start())
338 }
339 };
340
341 let indent = self.get_indentation(insert_pos);
343 let field_str = Self::format_field(&new_field);
344 let insert_text = if matches!(op.position, InsertPosition::First) {
345 format!("\n{}{},", indent, field_str)
346 } else {
347 format!("\n{}{},", indent, field_str)
348 };
349
350 self.content.insert_str(insert_pos, &insert_text);
351 return Ok(true);
352 }
353
354 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
355 }
356
357 pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
358 let is_enum_variant = op.struct_name.contains("::");
360
361 if is_enum_variant {
364 anyhow::bail!(
365 "Cannot update field in enum variant definition '{}'.\n\
366 To update fields in enum variant struct literals, use the transform command:\n\
367 rs-hack transform --node-type struct-literal --name {} --action replace --with <new_pattern> --paths ... --apply",
368 op.struct_name, op.struct_name
369 );
370 }
371
372 let item_struct = self.syntax_tree.items.iter()
374 .find_map(|item| {
375 if let Item::Struct(s) = item {
376 if s.ident == op.struct_name {
377 return Some(s.clone());
378 }
379 }
380 None
381 })
382 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
383
384 if let Some(ref where_filter) = op.where_filter {
386 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
387 return Ok(ModificationResult {
389 changed: false,
390 modified_nodes: vec![],
391 });
392 }
393 }
394
395 let backup_node = BackupNode {
397 node_type: "struct".to_string(),
398 identifier: op.struct_name.clone(),
399 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
400 location: self.span_to_location(item_struct.span()),
401 };
402
403 let modified = self.replace_struct_field(&item_struct, op)?;
404
405 Ok(ModificationResult {
406 changed: modified,
407 modified_nodes: if modified { vec![backup_node] } else { vec![] },
408 })
409 }
410
411 fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
412 if let Fields::Named(ref fields) = item_struct.fields {
413 let field_code = format!("struct Dummy {{ {} }}", op.field_def);
415 let dummy: ItemStruct = parse_str(&field_code)
416 .context("Failed to parse field definition")?;
417
418 let new_field = if let Fields::Named(ref nf) = dummy.fields {
419 nf.named.first()
420 .context("No field found in definition")?
421 .clone()
422 } else {
423 anyhow::bail!("Expected named field");
424 };
425
426 let field_name = new_field.ident.as_ref()
428 .map(|i| i.to_string())
429 .context("Field must have a name")?;
430
431 let existing_field = fields.named.iter()
433 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
434 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
435
436 let start = self.span_to_byte_offset(existing_field.span().start());
438 let end = self.span_to_byte_offset(existing_field.span().end());
439
440 let new_field_str = Self::format_field(&new_field);
442
443 self.content.replace_range(start..end, &new_field_str);
445
446 return Ok(true);
447 }
448
449 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
450 }
451
452 pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
453 let mut modified_nodes = Vec::new();
454 let mut changed = false;
455
456 let is_enum_variant = op.struct_name.contains("::");
458
459 let effective_literal_only = op.literal_only || is_enum_variant;
462
463 if !effective_literal_only {
465 let item_struct = self.syntax_tree.items.iter()
467 .find_map(|item| {
468 if let Item::Struct(s) = item {
469 if s.ident == op.struct_name {
470 return Some(s.clone());
471 }
472 }
473 None
474 })
475 .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
476
477 if let Some(ref where_filter) = op.where_filter {
479 if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
480 return Ok(ModificationResult {
482 changed: false,
483 modified_nodes: vec![],
484 });
485 }
486 }
487
488 let backup_node = BackupNode {
490 node_type: "struct".to_string(),
491 identifier: op.struct_name.clone(),
492 original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
493 location: self.span_to_location(item_struct.span()),
494 };
495
496 if let Fields::Named(ref fields) = item_struct.fields {
497 let field_to_remove = fields.named.iter()
499 .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()));
500
501 if field_to_remove.is_none() {
503 let field_names: Vec<String> = fields.named.iter()
504 .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
505 .collect();
506
507 let suggestions = Self::find_similar_fields(&op.field_name, &field_names);
508
509 if suggestions.is_empty() {
510 return Err(anyhow::anyhow!(
511 "Field '{}' not found in struct '{}'\n\nAvailable fields: {}",
512 op.field_name,
513 op.struct_name,
514 field_names.join(", ")
515 ));
516 } else {
517 return Err(anyhow::anyhow!(
518 "Field '{}' not found in struct '{}'\n\nDid you mean one of these?\n - {}\n\nAll available fields: {}",
519 op.field_name,
520 op.struct_name,
521 suggestions.join("\n - "),
522 field_names.join(", ")
523 ));
524 }
525 }
526
527 let field_to_remove = field_to_remove.unwrap();
528
529 let start = self.span_to_byte_offset(field_to_remove.span().start());
531 let mut end = self.span_to_byte_offset(field_to_remove.span().end());
532
533 while end < self.content.len() {
535 match self.content.as_bytes()[end] as char {
536 ',' => {
537 end += 1;
538 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
540 end += 1;
541 }
542 break;
543 }
544 ' ' | '\t' => end += 1,
545 '\n' => {
546 end += 1;
547 break;
548 }
549 _ => break,
550 }
551 }
552
553 let mut line_start = start;
555 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
556 line_start -= 1;
557 }
558
559 let before_field = &self.content[line_start..start];
561 if before_field.trim().is_empty() {
562 self.content.replace_range(line_start..end, "");
564 } else {
565 self.content.replace_range(start..end, "");
567 }
568
569 modified_nodes.push(backup_node);
570 changed = true;
571
572 self.syntax_tree = syn::parse_str(&self.content)?;
574 } else {
575 anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
576 }
577 }
578
579 let literal_backups = self.collect_struct_literal_backups(&op.struct_name, None);
582
583 use syn::visit::Visit;
585
586 struct FieldDeletionFinder<'a> {
587 struct_name: String,
588 field_name: String,
589 deletion_ranges: Vec<(usize, usize)>, editor: &'a RustEditor,
591 }
592
593 impl<'ast, 'a> Visit<'ast> for FieldDeletionFinder<'a> {
594 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
595 let matches = if self.struct_name.contains("::") {
597 if self.struct_name.starts_with("*::") {
598 let target_name = &self.struct_name[3..];
599 node.path.segments.last()
600 .map(|seg| seg.ident.to_string() == target_name)
601 .unwrap_or(false)
602 } else {
603 let path_str = node.path.segments.iter()
604 .map(|seg| seg.ident.to_string())
605 .collect::<Vec<_>>()
606 .join("::");
607 path_str == self.struct_name
608 }
609 } else {
610 node.path.segments.len() == 1
611 && node.path.segments.last()
612 .map(|seg| seg.ident.to_string())
613 .as_ref() == Some(&self.struct_name)
614 };
615
616 if matches {
617 let field_idx = node.fields.iter().position(|fv| {
619 if let syn::Member::Named(ident) = &fv.member {
620 ident.to_string() == self.field_name
621 } else {
622 false
623 }
624 });
625
626 if let Some(idx) = field_idx {
627 let field = &node.fields[idx];
628 let start = self.editor.span_to_byte_offset(field.span().start());
629 let mut end = self.editor.span_to_byte_offset(field.span().end());
630
631 let content_bytes = self.editor.content.as_bytes();
634 while end < content_bytes.len() {
635 match content_bytes[end] {
636 b',' => {
637 end += 1;
638 if end < content_bytes.len() && content_bytes[end] == b'\n' {
640 end += 1;
641 }
642 break;
643 }
644 b' ' | b'\t' => {
645 end += 1;
646 }
647 _ => break,
648 }
649 }
650
651 let mut line_start = start;
653 while line_start > 0 {
654 let ch = content_bytes[line_start - 1];
655 if ch == b'\n' {
656 break;
657 } else if ch == b' ' || ch == b'\t' {
658 line_start -= 1;
659 } else {
660 break;
661 }
662 }
663
664 self.deletion_ranges.push((line_start, end));
665 }
666 }
667
668 syn::visit::visit_expr_struct(self, node);
669 }
670 }
671
672 let mut finder = FieldDeletionFinder {
673 struct_name: op.struct_name.clone(),
674 field_name: op.field_name.clone(),
675 deletion_ranges: Vec::new(),
676 editor: self,
677 };
678
679 finder.visit_file(&self.syntax_tree);
680
681 if !finder.deletion_ranges.is_empty() {
682 let mut ranges = finder.deletion_ranges;
684 ranges.sort_by_key(|(start, _)| std::cmp::Reverse(*start));
685
686 for (start, end) in ranges {
688 self.content.drain(start..end);
689 }
690
691 self.syntax_tree = syn::parse_str(&self.content)
693 .context("Failed to re-parse after removing struct literal fields")?;
694 self.line_offsets = Self::compute_line_offsets(&self.content);
695
696 modified_nodes.extend(literal_backups);
697 changed = true;
698 }
699
700 Ok(ModificationResult {
701 changed,
702 modified_nodes,
703 })
704 }
705
706 pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
707 let field_name = op.field_def.split(':')
709 .next()
710 .map(|s| s.trim().to_string())
711 .context("Field definition must contain ':'")?;
712
713 let path_resolver = if let Some(struct_path) = &op.struct_path {
715 let mut resolver = PathResolver::new(struct_path)
716 .ok_or_else(|| anyhow::anyhow!("Invalid struct path: {}", struct_path))?;
717
718 resolver.scan_file(&self.syntax_tree);
720 Some(resolver)
721 } else {
722 None
723 };
724
725 let backup_nodes = self.collect_struct_literal_backups(&op.struct_name, path_resolver.as_ref());
727
728 use syn::visit::Visit;
730
731 struct LiteralFieldInserter<'a> {
732 struct_name: String,
733 field_name: String,
734 path_resolver: Option<&'a PathResolver>,
735 insertion_points: Vec<(usize, usize)>, editor: &'a RustEditor,
737 }
738
739 impl<'ast, 'a> Visit<'ast> for LiteralFieldInserter<'a> {
740 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
741 use syn::parse::Parser;
743
744 if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
745 .parse2(node.mac.tokens.clone())
746 {
747 for expr in exprs.iter() {
748 syn::visit::visit_expr(self, expr);
749 }
750 }
751
752 syn::visit::visit_expr_macro(self, node);
753 }
754
755 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
756 let is_match = if let Some(resolver) = &self.path_resolver {
758 resolver.matches_target(&node.path)
759 } else {
760 if self.struct_name.contains("::") {
762 if self.struct_name.starts_with("*::") {
763 let target_name = &self.struct_name[3..];
764 node.path.segments.last()
765 .map(|seg| seg.ident.to_string() == target_name)
766 .unwrap_or(false)
767 } else {
768 let path_str = node.path.segments.iter()
769 .map(|seg| seg.ident.to_string())
770 .collect::<Vec<_>>()
771 .join("::");
772 path_str == self.struct_name
773 }
774 } else {
775 node.path.segments.len() == 1
776 && node.path.segments.last()
777 .map(|seg| seg.ident.to_string())
778 .as_ref() == Some(&self.struct_name)
779 }
780 };
781
782 if is_match {
783 let field_exists = node.fields.iter().any(|fv| {
785 fv.member.to_token_stream().to_string() == self.field_name
786 });
787
788 if !field_exists {
789 let insert_offset = if let Some(last_field) = node.fields.last() {
791 self.editor.span_to_byte_offset(last_field.span().end())
793 } else {
794 let brace_pos = self.editor.span_to_byte_offset(node.brace_token.span.join().start());
796 brace_pos + 1 };
798
799 let indent = if let Some(last_field) = node.fields.last() {
801 let line_start = self.editor.span_to_byte_offset(last_field.span().start());
802 self.editor.get_indentation(line_start).len()
803 } else {
804 let struct_start = self.editor.span_to_byte_offset(node.span().start());
806 self.editor.get_indentation(struct_start).len() + 4
807 };
808
809 self.insertion_points.push((insert_offset, indent));
810 }
811 }
812
813 syn::visit::visit_expr_struct(self, node);
814 }
815 }
816
817 let mut inserter = LiteralFieldInserter {
818 struct_name: op.struct_name.clone(),
819 field_name: field_name.clone(),
820 path_resolver: path_resolver.as_ref(),
821 insertion_points: Vec::new(),
822 editor: self,
823 };
824
825 inserter.visit_file(&self.syntax_tree);
826
827 if inserter.insertion_points.is_empty() {
828 return Ok(ModificationResult {
829 changed: false,
830 modified_nodes: vec![],
831 });
832 }
833
834 let mut points = inserter.insertion_points;
837 points.sort_by_key(|(offset, _)| std::cmp::Reverse(*offset));
838
839 for (insert_offset, indent_spaces) in points {
841 let indent = " ".repeat(indent_spaces);
842 let field_str = format!(",\n{}{}", indent, op.field_def);
843 self.content.insert_str(insert_offset, &field_str);
844 }
845
846 self.syntax_tree = syn::parse_str(&self.content)
848 .context("Failed to re-parse after adding struct literal fields")?;
849 self.line_offsets = Self::compute_line_offsets(&self.content);
850
851 Ok(ModificationResult {
852 changed: true,
853 modified_nodes: backup_nodes,
854 })
855 }
856
857 fn collect_struct_literal_backups(&self, struct_name: &str, path_resolver: Option<&PathResolver>) -> Vec<BackupNode> {
859 use syn::visit::Visit;
860 use syn::spanned::Spanned;
861
862 struct LiteralCollector<'a> {
863 struct_name: String,
864 path_resolver: Option<&'a PathResolver>,
865 backups: Vec<BackupNode>,
866 counter: usize,
867 editor: &'a RustEditor,
868 }
869
870 impl<'ast, 'a> Visit<'ast> for LiteralCollector<'a> {
871 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
872 use syn::parse::Parser;
874
875 if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
876 .parse2(node.mac.tokens.clone())
877 {
878 for expr in exprs.iter() {
879 syn::visit::visit_expr(self, expr);
880 }
881 }
882
883 syn::visit::visit_expr_macro(self, node);
884 }
885
886 fn visit_expr(&mut self, node: &'ast Expr) {
887 if let Expr::Struct(expr_struct) = node {
888 let matches = if let Some(resolver) = self.path_resolver {
889 resolver.matches_target(&expr_struct.path)
891 } else {
892 if self.struct_name.contains("::") {
898 if self.struct_name.starts_with("*::") {
900 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
903 .map(|seg| seg.ident.to_string() == target_name)
904 .unwrap_or(false)
905 } else {
906 let path_str = expr_struct.path.segments.iter()
908 .map(|seg| seg.ident.to_string())
909 .collect::<Vec<_>>()
910 .join("::");
911 path_str == self.struct_name
912 }
913 } else {
914 expr_struct.path.segments.len() == 1
916 && expr_struct.path.segments.last()
917 .map(|seg| seg.ident.to_string() == self.struct_name)
918 .unwrap_or(false)
919 }
920 };
921
922 if matches {
923 let start = self.editor.span_to_byte_offset(expr_struct.span().start());
925 let end = self.editor.span_to_byte_offset(expr_struct.span().end());
926 let original_source = &self.editor.content[start..end];
927
928 self.backups.push(BackupNode {
929 node_type: "struct-literal".to_string(),
930 identifier: format!("{}#{}", self.struct_name, self.counter),
931 original_content: original_source.to_string(),
932 location: NodeLocation {
933 line: 0, column: 0,
935 end_line: 0,
936 end_column: 0,
937 },
938 });
939 self.counter += 1;
940 }
941 }
942 syn::visit::visit_expr(self, node);
943 }
944 }
945
946 let mut collector = LiteralCollector {
947 struct_name: struct_name.to_string(),
948 path_resolver,
949 backups: Vec::new(),
950 counter: 0,
951 editor: self,
952 };
953
954 collector.visit_file(&self.syntax_tree);
955 collector.backups
956 }
957
958 pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
959 let item_enum = self.syntax_tree.items.iter()
961 .find_map(|item| {
962 if let Item::Enum(e) = item {
963 if e.ident == op.enum_name {
964 return Some(e.clone());
965 }
966 }
967 None
968 })
969 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
970
971 if let Some(ref where_filter) = op.where_filter {
973 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
974 return Ok(ModificationResult {
976 changed: false,
977 modified_nodes: vec![],
978 });
979 }
980 }
981
982 let backup_node = BackupNode {
984 node_type: "enum".to_string(),
985 identifier: op.enum_name.clone(),
986 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
987 location: self.span_to_location(item_enum.span()),
988 };
989
990 let modified = self.insert_enum_variant(&item_enum, op)?;
991
992 Ok(ModificationResult {
993 changed: modified,
994 modified_nodes: if modified { vec![backup_node] } else { vec![] },
995 })
996 }
997
998 fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
999 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
1001 let dummy: ItemEnum = parse_str(&variant_code)
1002 .context("Failed to parse variant definition")?;
1003
1004 let new_variant = dummy.variants.first()
1005 .context("No variant found in definition")?
1006 .clone();
1007
1008 let variant_name = new_variant.ident.to_string();
1010 if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
1011 return Ok(false);
1013 }
1014
1015 let insert_pos = match &op.position {
1017 InsertPosition::First => {
1018 if let Some(first_var) = item_enum.variants.first() {
1019 self.span_to_byte_offset(first_var.span().start())
1020 } else {
1021 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
1022 brace_pos + 1
1023 }
1024 }
1025 InsertPosition::Last => {
1026 if let Some(last_var) = item_enum.variants.last() {
1027 let end = self.span_to_byte_offset(last_var.span().end());
1028 self.find_after_field_end(end)
1029 } else {
1030 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
1031 brace_pos + 1
1032 }
1033 }
1034 InsertPosition::After(name) => {
1035 let variant = item_enum.variants.iter()
1036 .find(|v| v.ident.to_string() == *name)
1037 .with_context(|| format!("Variant '{}' not found", name))?;
1038 let end = self.span_to_byte_offset(variant.span().end());
1039 self.find_after_field_end(end)
1040 }
1041 InsertPosition::Before(name) => {
1042 let variant = item_enum.variants.iter()
1043 .find(|v| v.ident.to_string() == *name)
1044 .with_context(|| format!("Variant '{}' not found", name))?;
1045 self.span_to_byte_offset(variant.span().start())
1046 }
1047 };
1048
1049 let indent = self.get_indentation(insert_pos);
1050 let variant_str = new_variant.to_token_stream().to_string();
1051 let insert_text = format!("\n{}{},", indent, variant_str);
1052
1053 self.content.insert_str(insert_pos, &insert_text);
1054 Ok(true)
1055 }
1056
1057 fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
1058 let item_enum = self.syntax_tree.items.iter()
1060 .find_map(|item| {
1061 if let Item::Enum(e) = item {
1062 if e.ident == op.enum_name {
1063 return Some(e.clone());
1064 }
1065 }
1066 None
1067 })
1068 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1069
1070 if let Some(ref where_filter) = op.where_filter {
1072 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1073 return Ok(ModificationResult {
1075 changed: false,
1076 modified_nodes: vec![],
1077 });
1078 }
1079 }
1080
1081 let backup_node = BackupNode {
1083 node_type: "enum".to_string(),
1084 identifier: op.enum_name.clone(),
1085 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1086 location: self.span_to_location(item_enum.span()),
1087 };
1088
1089 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
1091 let dummy: ItemEnum = parse_str(&variant_code)
1092 .context("Failed to parse variant definition")?;
1093
1094 let new_variant = dummy.variants.first()
1095 .context("No variant found in definition")?
1096 .clone();
1097
1098 let variant_name = new_variant.ident.to_string();
1099
1100 let existing_variant = item_enum.variants.iter()
1102 .find(|v| v.ident.to_string() == variant_name)
1103 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
1104
1105 let start = self.span_to_byte_offset(existing_variant.span().start());
1107 let end = self.span_to_byte_offset(existing_variant.span().end());
1108
1109 let variant_str = new_variant.to_token_stream().to_string();
1111 self.content.replace_range(start..end, &variant_str);
1112
1113 Ok(ModificationResult {
1114 changed: true,
1115 modified_nodes: vec![backup_node],
1116 })
1117 }
1118
1119 pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
1120 let item_enum = self.syntax_tree.items.iter()
1122 .find_map(|item| {
1123 if let Item::Enum(e) = item {
1124 if e.ident == op.enum_name {
1125 return Some(e.clone());
1126 }
1127 }
1128 None
1129 })
1130 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1131
1132 if let Some(ref where_filter) = op.where_filter {
1134 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1135 return Ok(ModificationResult {
1137 changed: false,
1138 modified_nodes: vec![],
1139 });
1140 }
1141 }
1142
1143 let backup_node = BackupNode {
1145 node_type: "enum".to_string(),
1146 identifier: op.enum_name.clone(),
1147 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1148 location: self.span_to_location(item_enum.span()),
1149 };
1150
1151 let variant_to_remove = item_enum.variants.iter()
1153 .find(|v| v.ident.to_string() == op.variant_name)
1154 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
1155
1156 let start = self.span_to_byte_offset(variant_to_remove.span().start());
1158 let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
1159
1160 while end < self.content.len() {
1162 match self.content.as_bytes()[end] as char {
1163 ',' => {
1164 end += 1;
1165 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
1166 end += 1;
1167 }
1168 break;
1169 }
1170 ' ' | '\t' => end += 1,
1171 '\n' => {
1172 end += 1;
1173 break;
1174 }
1175 _ => break,
1176 }
1177 }
1178
1179 let mut line_start = start;
1181 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1182 line_start -= 1;
1183 }
1184
1185 let before_variant = &self.content[line_start..start];
1186 if before_variant.trim().is_empty() {
1187 self.content.replace_range(line_start..end, "");
1188 } else {
1189 self.content.replace_range(start..end, "");
1190 }
1191
1192 Ok(ModificationResult {
1193 changed: true,
1194 modified_nodes: vec![backup_node],
1195 })
1196 }
1197
1198 pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1199 if op.auto_detect {
1200 self.add_missing_match_arms(op)
1202 } else {
1203 self.add_single_match_arm(op)
1205 }
1206 }
1207
1208 fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1209 let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
1211 let expr: syn::Expr = parse_str(&dummy_match)
1212 .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
1213
1214 let arm = if let syn::Expr::Match(match_expr) = expr {
1216 match_expr.arms.into_iter().next()
1217 .context("Failed to extract arm from dummy match")?
1218 } else {
1219 anyhow::bail!("Expected match expression");
1220 };
1221
1222 let backup_node = if let Some(ref fn_name) = op.function_name {
1224 self.get_function_backup(fn_name)?
1225 } else {
1226 BackupNode {
1229 node_type: "Unknown".to_string(),
1230 identifier: "match_expression".to_string(),
1231 original_content: String::new(),
1232 location: NodeLocation {
1233 line: 0,
1234 column: 0,
1235 end_line: 0,
1236 end_column: 0,
1237 },
1238 }
1239 };
1240
1241 let mut visitor = MatchArmAdder {
1243 target_function: op.function_name.clone(),
1244 arm_to_add: arm,
1245 modified: false,
1246 current_function: None,
1247 modified_function: None,
1248 };
1249
1250 visitor.visit_file_mut(&mut self.syntax_tree);
1251
1252 if visitor.modified {
1253 self.replace_modified_functions(&visitor.modified_function)?;
1255 Ok(ModificationResult {
1256 changed: true,
1257 modified_nodes: vec![backup_node],
1258 })
1259 } else {
1260 Ok(ModificationResult {
1261 changed: false,
1262 modified_nodes: vec![],
1263 })
1264 }
1265 }
1266
1267 fn unparse_item(&self, item: &Item) -> String {
1269 let temp_file = syn::File {
1270 shebang: None,
1271 attrs: Vec::new(),
1272 items: vec![item.clone()],
1273 };
1274 prettyplease::unparse(&temp_file).trim().to_string()
1275 }
1276
1277 fn reformat_item_isolated<F>(&mut self, predicate: F) -> Result<bool>
1280 where
1281 F: Fn(&Item) -> bool,
1282 {
1283 let original_syntax_tree: syn::File = syn::parse_str(&self.content)
1285 .context("Failed to parse original content")?;
1286
1287 let (item_index, original_item) = original_syntax_tree.items.iter()
1288 .enumerate()
1289 .find(|(_, item)| predicate(item))
1290 .ok_or_else(|| anyhow::anyhow!("Item not found"))?;
1291
1292 let start = self.span_to_byte_offset(original_item.span().start());
1294 let end = self.span_to_byte_offset(original_item.span().end());
1295
1296 if item_index >= self.syntax_tree.items.len() {
1298 anyhow::bail!("Item index out of bounds after modification");
1299 }
1300 let modified_item = &self.syntax_tree.items[item_index];
1301
1302 let formatted_item = self.unparse_item(modified_item);
1304
1305 self.content.replace_range(start..end, &formatted_item);
1307
1308 self.syntax_tree = syn::parse_str(&self.content)
1310 .context("Failed to re-parse after isolated prettyplease")?;
1311 self.line_offsets = Self::compute_line_offsets(&self.content);
1312
1313 Ok(true)
1314 }
1315
1316 fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
1318 for item in &self.syntax_tree.items {
1319 if let Item::Fn(f) = item {
1320 if f.sig.ident == fn_name {
1321 return Ok(BackupNode {
1322 node_type: "function".to_string(),
1323 identifier: fn_name.to_string(),
1324 original_content: self.unparse_item(&Item::Fn(f.clone())),
1325 location: self.span_to_location(f.span()),
1326 });
1327 }
1328 }
1329 }
1330 anyhow::bail!("Function '{}' not found", fn_name)
1331 }
1332
1333 fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1334 let enum_name = op.enum_name.as_ref()
1336 .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
1337
1338 let enum_variants = self.find_enum_variants(enum_name)?;
1340
1341 if enum_variants.is_empty() {
1342 anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
1343 }
1344
1345 let existing_patterns = self.find_existing_match_patterns(&op.function_name);
1347
1348 let mut missing_variants = Vec::new();
1350 for variant in &enum_variants {
1351 let pattern = format!("{}::{}", enum_name, variant);
1352 let pattern_normalized = pattern.replace(" ", "");
1353
1354 let exists = existing_patterns.iter().any(|p| {
1355 p.replace(" ", "") == pattern_normalized
1356 });
1357
1358 if !exists {
1359 missing_variants.push(variant.clone());
1360 }
1361 }
1362
1363 if missing_variants.is_empty() {
1364 println!("All enum variants already covered in match expressions");
1365 return Ok(ModificationResult {
1366 changed: false,
1367 modified_nodes: vec![],
1368 });
1369 }
1370
1371 let backup_node = if let Some(ref fn_name) = op.function_name {
1373 self.get_function_backup(fn_name)?
1374 } else {
1375 BackupNode {
1376 node_type: "Unknown".to_string(),
1377 identifier: "match_expression".to_string(),
1378 original_content: String::new(),
1379 location: NodeLocation {
1380 line: 0,
1381 column: 0,
1382 end_line: 0,
1383 end_column: 0,
1384 },
1385 }
1386 };
1387
1388 let mut arms_to_add = Vec::new();
1390 for variant in &missing_variants {
1391 let pattern = format!("{}::{}", enum_name, variant);
1392 let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
1393 let expr: syn::Expr = parse_str(&dummy_match)
1394 .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
1395
1396 if let syn::Expr::Match(match_expr) = expr {
1397 if let Some(arm) = match_expr.arms.into_iter().next() {
1398 arms_to_add.push((pattern.clone(), arm));
1399 }
1400 }
1401 }
1402
1403 let mut visitor = MultiMatchArmAdder {
1405 target_function: op.function_name.clone(),
1406 arms_to_add,
1407 modified: false,
1408 current_function: None,
1409 modified_function: None,
1410 };
1411
1412 visitor.visit_file_mut(&mut self.syntax_tree);
1413
1414 if visitor.modified {
1415 for variant in &missing_variants {
1417 println!("Added match arm for: {}::{}", enum_name, variant);
1418 }
1419
1420 self.replace_modified_functions(&visitor.modified_function)?;
1422 Ok(ModificationResult {
1423 changed: true,
1424 modified_nodes: vec![backup_node],
1425 })
1426 } else {
1427 Ok(ModificationResult {
1428 changed: false,
1429 modified_nodes: vec![],
1430 })
1431 }
1432 }
1433
1434 fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
1435 for item in &self.syntax_tree.items {
1437 if let Item::Enum(e) = item {
1438 if e.ident == enum_name {
1439 let variants: Vec<String> = e.variants.iter()
1440 .map(|v| v.ident.to_string())
1441 .collect();
1442 return Ok(variants);
1443 }
1444 }
1445 }
1446
1447 Ok(Vec::new())
1448 }
1449
1450 fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1451 use syn::visit::Visit;
1452
1453 struct PatternCollector {
1454 target_function: Option<String>,
1455 current_function: Option<String>,
1456 patterns: Vec<String>,
1457 }
1458
1459 impl<'ast> Visit<'ast> for PatternCollector {
1460 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1461 let prev_fn = self.current_function.clone();
1462 self.current_function = Some(node.sig.ident.to_string());
1463 syn::visit::visit_item_fn(self, node);
1464 self.current_function = prev_fn;
1465 }
1466
1467 fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1468 if let Some(ref target) = self.target_function {
1470 if self.current_function.as_ref() != Some(target) {
1471 syn::visit::visit_expr_match(self, node);
1472 return;
1473 }
1474 }
1475
1476 for arm in &node.arms {
1478 self.patterns.push(arm.pat.to_token_stream().to_string());
1479 }
1480
1481 syn::visit::visit_expr_match(self, node);
1482 }
1483 }
1484
1485 let mut collector = PatternCollector {
1486 target_function: function_name.clone(),
1487 current_function: None,
1488 patterns: Vec::new(),
1489 };
1490
1491 collector.visit_file(&self.syntax_tree);
1492 collector.patterns
1493 }
1494
1495 pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1496 let backup_node = if let Some(ref fn_name) = op.function_name {
1498 self.get_function_backup(fn_name)?
1499 } else {
1500 BackupNode {
1501 node_type: "Unknown".to_string(),
1502 identifier: "match_expression".to_string(),
1503 original_content: String::new(),
1504 location: NodeLocation {
1505 line: 0,
1506 column: 0,
1507 end_line: 0,
1508 end_column: 0,
1509 },
1510 }
1511 };
1512
1513 let new_body: syn::Expr = parse_str(&op.new_body)
1515 .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1516
1517 let mut visitor = MatchArmUpdater {
1519 target_function: op.function_name.clone(),
1520 pattern_to_match: op.pattern.clone(),
1521 new_body,
1522 modified: false,
1523 current_function: None,
1524 modified_function: None,
1525 };
1526
1527 visitor.visit_file_mut(&mut self.syntax_tree);
1528
1529 if visitor.modified {
1530 self.replace_modified_functions(&visitor.modified_function)?;
1532 Ok(ModificationResult {
1533 changed: true,
1534 modified_nodes: vec![backup_node],
1535 })
1536 } else {
1537 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1538 }
1539 }
1540
1541 pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1542 let backup_node = if let Some(ref fn_name) = op.function_name {
1544 self.get_function_backup(fn_name)?
1545 } else {
1546 BackupNode {
1547 node_type: "Unknown".to_string(),
1548 identifier: "match_expression".to_string(),
1549 original_content: String::new(),
1550 location: NodeLocation {
1551 line: 0,
1552 column: 0,
1553 end_line: 0,
1554 end_column: 0,
1555 },
1556 }
1557 };
1558
1559 let mut visitor = MatchArmRemover {
1561 target_function: op.function_name.clone(),
1562 pattern_to_remove: op.pattern.clone(),
1563 modified: false,
1564 current_function: None,
1565 modified_function: None,
1566 };
1567
1568 visitor.visit_file_mut(&mut self.syntax_tree);
1569
1570 if visitor.modified {
1571 self.replace_modified_functions(&visitor.modified_function)?;
1573 Ok(ModificationResult {
1574 changed: true,
1575 modified_nodes: vec![backup_node],
1576 })
1577 } else {
1578 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1579 }
1580 }
1581
1582 pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1583 let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1585 let dummy: syn::ItemImpl = parse_str(&method_code)
1586 .context("Failed to parse method definition")?;
1587
1588 let new_method = dummy.items.first()
1589 .context("No method found in definition")?
1590 .clone();
1591
1592 let method_name = match &new_method {
1594 syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1595 _ => anyhow::bail!("Only method definitions are supported"),
1596 };
1597
1598 let impl_index = self.syntax_tree.items.iter().position(|item| {
1600 if let Item::Impl(impl_block) = item {
1601 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1603 if let Some(segment) = type_path.path.segments.last() {
1604 return segment.ident == op.target;
1605 }
1606 }
1607 }
1608 false
1609 }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1610
1611 let impl_block = match &self.syntax_tree.items[impl_index] {
1613 Item::Impl(i) => i,
1614 _ => unreachable!(),
1615 };
1616
1617 let method_exists = impl_block.items.iter().any(|item| {
1618 if let syn::ImplItem::Fn(f) = item {
1619 f.sig.ident == method_name
1620 } else {
1621 false
1622 }
1623 });
1624
1625 if method_exists {
1626 return Ok(ModificationResult {
1627 changed: false,
1628 modified_nodes: vec![],
1629 });
1630 }
1631
1632 let backup_node = BackupNode {
1634 node_type: "ItemImpl".to_string(),
1635 identifier: op.target.clone(),
1636 original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1637 location: self.span_to_location(impl_block.span()),
1638 };
1639
1640 let impl_span = impl_block.span();
1642
1643 match &mut self.syntax_tree.items[impl_index] {
1645 Item::Impl(impl_block) => {
1646 match &op.position {
1648 InsertPosition::First => {
1649 impl_block.items.insert(0, new_method);
1650 }
1651 InsertPosition::Last => {
1652 impl_block.items.push(new_method);
1653 }
1654 InsertPosition::After(name) => {
1655 let pos = impl_block.items.iter().position(|item| {
1656 if let syn::ImplItem::Fn(f) = item {
1657 f.sig.ident == name
1658 } else {
1659 false
1660 }
1661 }).with_context(|| format!("Method '{}' not found", name))?;
1662 impl_block.items.insert(pos + 1, new_method);
1663 }
1664 InsertPosition::Before(name) => {
1665 let pos = impl_block.items.iter().position(|item| {
1666 if let syn::ImplItem::Fn(f) = item {
1667 f.sig.ident == name
1668 } else {
1669 false
1670 }
1671 }).with_context(|| format!("Method '{}' not found", name))?;
1672 impl_block.items.insert(pos, new_method);
1673 }
1674 }
1675 }
1676 _ => unreachable!(),
1677 }
1678
1679 self.replace_formatted_item(impl_index, impl_span)?;
1681
1682 Ok(ModificationResult {
1683 changed: true,
1684 modified_nodes: vec![backup_node],
1685 })
1686 }
1687
1688 pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1689 let use_code = format!("use {};", op.use_path);
1691 let use_item: syn::ItemUse = parse_str(&use_code)
1692 .context("Failed to parse use statement")?;
1693
1694 let use_exists = self.syntax_tree.items.iter().any(|item| {
1696 if let Item::Use(existing_use) = item {
1697 existing_use.tree.to_token_stream().to_string() ==
1699 use_item.tree.to_token_stream().to_string()
1700 } else {
1701 false
1702 }
1703 });
1704
1705 if use_exists {
1706 return Ok(ModificationResult {
1707 changed: false,
1708 modified_nodes: vec![],
1709 });
1710 }
1711
1712 let backup_node = BackupNode {
1714 node_type: "ItemUse".to_string(),
1715 identifier: op.use_path.clone(),
1716 original_content: format!("use {};", op.use_path),
1717 location: NodeLocation {
1718 line: 0,
1719 column: 0,
1720 end_line: 0,
1721 end_column: 0,
1722 },
1723 };
1724
1725 let insert_index = match &op.position {
1727 InsertPosition::First => 0,
1728 InsertPosition::Last => {
1729 self.syntax_tree.items.iter()
1731 .rposition(|item| matches!(item, Item::Use(_)))
1732 .map(|i| i + 1)
1733 .unwrap_or(0)
1734 }
1735 InsertPosition::After(path) => {
1736 let pos = self.syntax_tree.items.iter().position(|item| {
1738 if let Item::Use(u) = item {
1739 u.tree.to_token_stream().to_string().contains(path)
1740 } else {
1741 false
1742 }
1743 }).with_context(|| format!("Use statement for '{}' not found", path))?;
1744 pos + 1
1745 }
1746 InsertPosition::Before(path) => {
1747 self.syntax_tree.items.iter().position(|item| {
1749 if let Item::Use(u) = item {
1750 u.tree.to_token_stream().to_string().contains(path)
1751 } else {
1752 false
1753 }
1754 }).with_context(|| format!("Use statement for '{}' not found", path))?
1755 }
1756 };
1757
1758 self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1760
1761 let insert_line_pos = if insert_index == 0 {
1764 0
1766 } else {
1767 let prev_item = &self.syntax_tree.items[insert_index - 1];
1769 let span = prev_item.span();
1770 let end_pos = self.span_to_byte_offset(span.end());
1771
1772 let mut line_end = end_pos;
1774 while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1775 line_end += 1;
1776 }
1777 if line_end < self.content.len() {
1779 line_end + 1
1780 } else {
1781 self.content.push('\n');
1783 self.content.len()
1784 }
1785 };
1786
1787 let use_str = format!("use {};\n", op.use_path);
1789
1790 self.content.insert_str(insert_line_pos, &use_str);
1792
1793 Ok(ModificationResult {
1794 changed: true,
1795 modified_nodes: vec![backup_node],
1796 })
1797 }
1798
1799 pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1800 let item_index = self.syntax_tree.items.iter().position(|item| {
1802 match (&op.target_type as &str, item) {
1803 ("struct", Item::Struct(s)) => s.ident == op.target_name,
1804 ("enum", Item::Enum(e)) => e.ident == op.target_name,
1805 _ => false,
1806 }
1807 }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1808
1809 let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1811 Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1812 Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1813 _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1814 };
1815
1816 if let Some(ref where_filter) = op.where_filter {
1818 if !self.matches_where_filter(item_attrs, where_filter)? {
1819 return Ok(ModificationResult {
1821 changed: false,
1822 modified_nodes: vec![],
1823 });
1824 }
1825 }
1826
1827 let backup_node = BackupNode {
1829 node_type: if op.target_type == "struct" { "struct" } else { "enum" }.to_string(),
1830 identifier: op.target_name.clone(),
1831 original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1832 location: self.span_to_location(item_span),
1833 };
1834
1835 let new_derives: Vec<String> = op.derives.iter()
1837 .filter(|d| !existing_derives.contains(&d.to_string()))
1838 .cloned()
1839 .collect();
1840
1841 if new_derives.is_empty() {
1842 return Ok(ModificationResult {
1844 changed: false,
1845 modified_nodes: vec![],
1846 });
1847 }
1848
1849 let mut all_derives = existing_derives;
1851 all_derives.extend(new_derives);
1852
1853 let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1855
1856 match &mut self.syntax_tree.items[item_index] {
1858 Item::Struct(s) => {
1859 Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1860 }
1861 Item::Enum(e) => {
1862 Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1863 }
1864 _ => unreachable!(),
1865 }
1866
1867 self.replace_formatted_item(item_index, item_span)?;
1869
1870 Ok(ModificationResult {
1871 changed: true,
1872 modified_nodes: vec![backup_node],
1873 })
1874 }
1875
1876 fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1878 let item_start_pos = self.span_to_byte_offset(original_span.start());
1880 let item_end_pos = self.span_to_byte_offset(original_span.end());
1881
1882 let mut actual_start = item_start_pos;
1884
1885 let mut temp_pos = item_start_pos;
1887 while temp_pos > 0 {
1888 temp_pos = temp_pos.saturating_sub(1);
1890 let mut line_start = temp_pos;
1891 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1892 line_start -= 1;
1893 }
1894
1895 let line = if temp_pos < self.content.len() {
1896 &self.content[line_start..temp_pos + 1]
1897 } else {
1898 &self.content[line_start..]
1899 };
1900 let trimmed = line.trim();
1901
1902 if trimmed.starts_with("#[") {
1903 actual_start = line_start;
1904 temp_pos = line_start;
1905 } else if trimmed.is_empty() {
1906 temp_pos = line_start;
1907 } else {
1908 break;
1909 }
1910
1911 if line_start == 0 {
1912 break;
1913 }
1914 }
1915
1916 let item_clone = self.syntax_tree.items[item_index].clone();
1918 let temp_file = syn::File {
1919 shebang: None,
1920 attrs: Vec::new(),
1921 items: vec![item_clone],
1922 };
1923
1924 let formatted = prettyplease::unparse(&temp_file);
1926 let formatted = formatted.trim();
1927
1928 self.content.replace_range(actual_start..item_end_pos, formatted);
1930
1931 Ok(())
1932 }
1933
1934 fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1936 for attr in attrs {
1937 if attr.path().is_ident("derive") {
1938 if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1939 let tokens_str = meta_list.tokens.to_string();
1940 return tokens_str
1941 .split(',')
1942 .map(|s| s.trim().to_string())
1943 .collect();
1944 }
1945 }
1946 }
1947 Vec::new()
1948 }
1949
1950 fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1955 if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1957 let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1958 let existing_derives = Self::extract_derives(attrs);
1959
1960 for required_trait in required_traits {
1962 if existing_derives.iter().any(|d| d == required_trait) {
1963 return Ok(true);
1964 }
1965 }
1966 return Ok(false);
1967 }
1968
1969 Ok(true)
1971 }
1972
1973 fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1975 let derive_str = derives.join(", ");
1976
1977 let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1979 let parsed: syn::ItemStruct = parse_str(&dummy)
1980 .context("Failed to parse derive attribute")?;
1981
1982 let new_attr = parsed.attrs.into_iter()
1983 .find(|a| a.path().is_ident("derive"))
1984 .context("Failed to extract derive attribute")?;
1985
1986 if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1988 attrs[pos] = new_attr;
1989 } else {
1990 attrs.insert(0, new_attr);
1992 }
1993
1994 Ok(())
1995 }
1996
1997 fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1999 if modified_function.is_none() {
2001 self.content = prettyplease::unparse(&self.syntax_tree);
2004 return Ok(());
2005 }
2006
2007 let original_syntax_tree: File = syn::parse_str(&self.content)
2009 .context("Failed to re-parse original content")?;
2010
2011 let function_name = modified_function.as_ref().unwrap();
2012
2013 let original_fn = original_syntax_tree.items.iter()
2015 .find_map(|item| {
2016 if let Item::Fn(f) = item {
2017 if f.sig.ident == function_name {
2018 return Some(f.clone());
2019 }
2020 }
2021 None
2022 })
2023 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
2024
2025 let start = self.span_to_byte_offset(original_fn.span().start());
2027 let end = self.span_to_byte_offset(original_fn.span().end());
2028
2029 let modified_fn = self.syntax_tree.items.iter()
2031 .find_map(|item| {
2032 if let Item::Fn(f) = item {
2033 if f.sig.ident == function_name {
2034 return Some(f.clone());
2035 }
2036 }
2037 None
2038 })
2039 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
2040
2041 let dummy_file = syn::File {
2043 shebang: None,
2044 attrs: Vec::new(),
2045 items: vec![Item::Fn(modified_fn)],
2046 };
2047
2048 let formatted_fn = prettyplease::unparse(&dummy_file);
2049
2050 let formatted_fn = formatted_fn.trim();
2052
2053 self.content.replace_range(start..end, formatted_fn);
2055
2056 Ok(())
2057 }
2058
2059 pub fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
2060 let line_idx = pos.line.saturating_sub(1);
2061 if line_idx < self.line_offsets.len() {
2062 self.line_offsets[line_idx] + pos.column
2063 } else {
2064 self.content.len()
2065 }
2066 }
2067
2068 fn find_after_field_end(&self, pos: usize) -> usize {
2069 let mut i = pos;
2071 while i < self.content.len() {
2072 match self.content.as_bytes()[i] as char {
2073 ',' => return i + 1,
2074 '\n' => return i + 1,
2075 _ => i += 1,
2076 }
2077 }
2078 pos
2079 }
2080
2081 fn get_indentation(&self, pos: usize) -> String {
2082 let mut line_start = pos;
2084 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
2085 line_start -= 1;
2086 }
2087
2088 let mut indent = String::new();
2090 let mut i = line_start;
2091 while i < self.content.len() {
2092 match self.content.as_bytes()[i] as char {
2093 ' ' | '\t' => {
2094 indent.push(self.content.as_bytes()[i] as char);
2095 i += 1;
2096 }
2097 _ => break,
2098 }
2099 }
2100
2101 if indent.is_empty() {
2103 " ".to_string()
2104 } else {
2105 indent
2106 }
2107 }
2108
2109 pub fn to_string(&self) -> String {
2110 self.content.clone()
2111 }
2112
2113 pub fn get_syntax_tree(&self) -> &syn::File {
2115 &self.syntax_tree
2116 }
2117
2118 pub fn replace_range(&mut self, start: usize, end: usize, new_content: &str) -> Result<()> {
2120 if start > end || end > self.content.len() {
2121 anyhow::bail!("Invalid range: {}..{} (content length: {})", start, end, self.content.len());
2122 }
2123
2124 self.content.replace_range(start..end, new_content);
2125
2126 self.syntax_tree = syn::parse_str(&self.content)
2128 .context("Failed to re-parse after replace_range")?;
2129 self.line_offsets = Self::compute_line_offsets(&self.content);
2130
2131 Ok(())
2132 }
2133
2134 pub fn find_field_locations(&self, field_name: &str) -> Result<Vec<crate::operations::FieldLocation>> {
2136 use syn::visit::Visit;
2137 use crate::operations::{FieldLocation, FieldContext};
2138
2139 let mut locations = Vec::new();
2140
2141 for item in &self.syntax_tree.items {
2143 if let Item::Struct(s) = item {
2144 if let Fields::Named(ref fields) = s.fields {
2145 for field in &fields.named {
2146 if let Some(ident) = &field.ident {
2147 if ident == field_name {
2148 let field_type = quote::quote!(#field).to_string()
2149 .split(':')
2150 .nth(1)
2151 .map(|s| s.trim().to_string())
2152 .unwrap_or_else(|| "unknown".to_string());
2153 locations.push(FieldLocation {
2154 file_path: String::new(),
2155 line: s.span().start().line,
2156 context: FieldContext::StructDefinition {
2157 struct_name: s.ident.to_string(),
2158 field_type,
2159 },
2160 });
2161 }
2162 }
2163 }
2164 }
2165 }
2166
2167 if let Item::Enum(e) = item {
2169 for variant in &e.variants {
2170 if let Fields::Named(ref fields) = variant.fields {
2171 for field in &fields.named {
2172 if let Some(ident) = &field.ident {
2173 if ident == field_name {
2174 let field_type = quote::quote!(#field).to_string()
2175 .split(':')
2176 .nth(1)
2177 .map(|s| s.trim().to_string())
2178 .unwrap_or_else(|| "unknown".to_string());
2179 locations.push(FieldLocation {
2180 file_path: String::new(),
2181 line: variant.span().start().line,
2182 context: FieldContext::EnumVariantDefinition {
2183 enum_name: e.ident.to_string(),
2184 variant_name: variant.ident.to_string(),
2185 field_type,
2186 },
2187 });
2188 }
2189 }
2190 }
2191 }
2192 }
2193 }
2194 }
2195
2196 struct LiteralVisitor<'a> {
2198 field_name: &'a str,
2199 locations: Vec<FieldLocation>,
2200 }
2201
2202 impl<'ast, 'a> Visit<'ast> for LiteralVisitor<'a> {
2203 fn visit_expr(&mut self, node: &'ast Expr) {
2204 if let Expr::Struct(expr_struct) = node {
2205 for field_value in &expr_struct.fields {
2207 if let syn::Member::Named(ident) = &field_value.member {
2208 if ident == self.field_name {
2209 let struct_name = expr_struct.path.segments.iter()
2210 .map(|seg| seg.ident.to_string())
2211 .collect::<Vec<_>>()
2212 .join("::");
2213
2214 self.locations.push(FieldLocation {
2215 file_path: String::new(),
2216 line: expr_struct.span().start().line,
2217 context: FieldContext::StructLiteral {
2218 struct_name,
2219 },
2220 });
2221 break;
2222 }
2223 }
2224 }
2225 }
2226 syn::visit::visit_expr(self, node);
2227 }
2228 }
2229
2230 let mut visitor = LiteralVisitor {
2231 field_name,
2232 locations: Vec::new(),
2233 };
2234
2235 visitor.visit_file(&self.syntax_tree);
2236 locations.extend(visitor.locations);
2237
2238 Ok(locations)
2239 }
2240
2241 pub fn inspect(&self, node_type: Option<&str>, name_filter: Option<&str>, variant_filter: Option<&str>, include_comments: bool) -> Result<Vec<crate::operations::InspectResult>> {
2243 use syn::visit::Visit;
2244 use crate::operations::InspectResult;
2245
2246 let mut results = Vec::new();
2247
2248 if node_type.is_none() {
2250 let all_types = vec![
2251 "struct", "enum", "function", "impl-method", "trait", "const", "static", "type-alias", "mod",
2252 "struct-literal", "match-arm", "enum-usage", "function-call", "method-call", "macro-call", "identifier", "type-ref",
2253 ];
2254 for nt in all_types {
2255 let mut type_results = self.inspect(Some(nt), name_filter, variant_filter, include_comments)?;
2256 results.append(&mut type_results);
2257 }
2258 return Ok(results);
2259 }
2260
2261 let node_type = node_type.unwrap(); match node_type {
2264 "struct-literal" => {
2265 struct StructLiteralVisitor<'a> {
2267 results: &'a mut Vec<InspectResult>,
2268 name_filter: Option<&'a str>,
2269 editor: &'a RustEditor,
2270 include_comments: bool,
2271 }
2272
2273 impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
2274 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2275 use syn::parse::Parser;
2278
2279 if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
2281 .parse2(node.mac.tokens.clone())
2282 {
2283 for expr in exprs.iter() {
2284 syn::visit::visit_expr(self, expr);
2285 }
2286 }
2287
2288 syn::visit::visit_expr_macro(self, node);
2290 }
2291
2292 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
2293 let filter = match self.name_filter {
2299 Some(f) => f,
2300 None => {
2301 let struct_name = node.path.segments.last()
2303 .map(|seg| seg.ident.to_string())
2304 .unwrap_or_default();
2305
2306 let snippet = self.editor.format_expr_struct(node);
2307 let location = self.editor.span_to_location(node.span());
2308
2309 let preceding_comment = if self.include_comments {
2311 extract_preceding_comment(&self.editor.content, location.line)
2312 } else {
2313 None
2314 };
2315
2316 let preceding_comment = if self.include_comments {
2318 extract_preceding_comment(&self.editor.content, location.line)
2319 } else {
2320 None
2321 };
2322
2323 self.results.push(InspectResult {
2324 file_path: String::new(),
2325 node_type: "struct-literal".to_string(),
2326 identifier: struct_name,
2327 location,
2328 snippet,
2329 preceding_comment,
2330 });
2331
2332 syn::visit::visit_expr_struct(self, node);
2333 return;
2334 }
2335 };
2336
2337 let matches = if filter.contains("::") {
2339 if filter.starts_with("*::") {
2341 let target_name = &filter[3..]; node.path.segments.last()
2344 .map(|seg| seg.ident.to_string() == target_name)
2345 .unwrap_or(false)
2346 } else {
2347 let path_str = node.path.segments.iter()
2349 .map(|seg| seg.ident.to_string())
2350 .collect::<Vec<_>>()
2351 .join("::");
2352 path_str == filter
2353 }
2354 } else {
2355 node.path.segments.last()
2358 .map(|seg| seg.ident.to_string() == filter)
2359 .unwrap_or(false)
2360 };
2361
2362 if !matches {
2363 syn::visit::visit_expr_struct(self, node);
2364 return;
2365 }
2366
2367 let struct_name = node.path.segments.last()
2369 .map(|seg| seg.ident.to_string())
2370 .unwrap_or_default();
2371
2372 let snippet = self.editor.format_expr_struct(node);
2374 let location = self.editor.span_to_location(node.span());
2375
2376 let preceding_comment = if self.include_comments {
2378 extract_preceding_comment(&self.editor.content, location.line)
2379 } else {
2380 None
2381 };
2382
2383 let preceding_comment = if self.include_comments {
2385 extract_preceding_comment(&self.editor.content, location.line)
2386 } else {
2387 None
2388 };
2389
2390 self.results.push(InspectResult {
2391 file_path: String::new(), node_type: "struct-literal".to_string(),
2393 identifier: struct_name,
2394 location,
2395 snippet,
2396 preceding_comment,
2397 });
2398
2399 syn::visit::visit_expr_struct(self, node);
2401 }
2402 }
2403
2404 let mut visitor = StructLiteralVisitor {
2405 results: &mut results,
2406 name_filter,
2407 editor: self,
2408 include_comments,
2409 };
2410
2411 for item in &self.syntax_tree.items {
2413 syn::visit::visit_item(&mut visitor, item);
2414 }
2415 }
2416 "match-arm" => {
2417 struct MatchArmVisitor<'a> {
2419 results: &'a mut Vec<InspectResult>,
2420 pattern_filter: Option<&'a str>,
2421 editor: &'a RustEditor,
2422 include_comments: bool,
2423 }
2424
2425 impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
2426 fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
2427 for arm in &node.arms {
2429 let pat = &arm.pat;
2431 let pattern_str = quote::quote!(#pat).to_string();
2432
2433 if let Some(filter) = self.pattern_filter {
2435 let normalized_pattern = pattern_str.replace(" ", "");
2437 let normalized_filter = filter.replace(" ", "");
2438
2439 if !normalized_pattern.contains(&normalized_filter) {
2440 continue;
2441 }
2442 }
2443
2444 let snippet = self.editor.format_match_arm(arm);
2446 let location = self.editor.span_to_location(arm.span());
2447
2448 let preceding_comment = if self.include_comments {
2450 extract_preceding_comment(&self.editor.content, location.line)
2451 } else {
2452 None
2453 };
2454
2455 let preceding_comment = if self.include_comments {
2457 extract_preceding_comment(&self.editor.content, location.line)
2458 } else {
2459 None
2460 };
2461
2462 self.results.push(InspectResult {
2463 file_path: String::new(), node_type: "match-arm".to_string(),
2465 identifier: pattern_str.replace(" ", ""),
2466 location,
2467 snippet,
2468 preceding_comment,
2469 });
2470 }
2471
2472 syn::visit::visit_expr_match(self, node);
2474 }
2475 }
2476
2477 let mut visitor = MatchArmVisitor {
2478 results: &mut results,
2479 pattern_filter: name_filter,
2480 editor: self,
2481 include_comments,
2482 };
2483
2484 for item in &self.syntax_tree.items {
2486 syn::visit::visit_item(&mut visitor, item);
2487 }
2488 }
2489 "enum-usage" => {
2490 struct EnumUsageVisitor<'a> {
2492 results: &'a mut Vec<InspectResult>,
2493 path_filter: Option<&'a str>,
2494 editor: &'a RustEditor,
2495 include_comments: bool,
2496 }
2497
2498 impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
2499 fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
2500 let path = &node.path;
2502 let path_str = quote::quote!(#path).to_string();
2503
2504 if let Some(filter) = self.path_filter {
2506 let normalized_path = path_str.replace(" ", "");
2508 let normalized_filter = filter.replace(" ", "");
2509
2510 if !normalized_path.contains(&normalized_filter) {
2511 syn::visit::visit_expr_path(self, node);
2512 return;
2513 }
2514 }
2515
2516 let snippet = self.editor.format_expr_path(node);
2518 let location = self.editor.span_to_location(node.span());
2519
2520 let preceding_comment = if self.include_comments {
2522 extract_preceding_comment(&self.editor.content, location.line)
2523 } else {
2524 None
2525 };
2526
2527 let preceding_comment = if self.include_comments {
2529 extract_preceding_comment(&self.editor.content, location.line)
2530 } else {
2531 None
2532 };
2533
2534 self.results.push(InspectResult {
2535 file_path: String::new(), node_type: "enum-usage".to_string(),
2537 identifier: path_str.replace(" ", ""),
2538 location,
2539 snippet,
2540 preceding_comment,
2541 });
2542
2543 syn::visit::visit_expr_path(self, node);
2545 }
2546 }
2547
2548 let mut visitor = EnumUsageVisitor {
2549 results: &mut results,
2550 path_filter: name_filter,
2551 editor: self,
2552 include_comments,
2553 };
2554
2555 for item in &self.syntax_tree.items {
2557 syn::visit::visit_item(&mut visitor, item);
2558 }
2559 }
2560 "function-call" => {
2561 struct FunctionCallVisitor<'a> {
2563 results: &'a mut Vec<InspectResult>,
2564 name_filter: Option<&'a str>,
2565 editor: &'a RustEditor,
2566 include_comments: bool,
2567 }
2568
2569 impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
2570 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
2571 let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
2573 expr_path.path.segments.last()
2575 .map(|seg| seg.ident.to_string())
2576 .unwrap_or_default()
2577 } else {
2578 quote::quote!(#node.func).to_string()
2580 };
2581
2582 if let Some(filter) = self.name_filter {
2584 if func_name != filter {
2585 syn::visit::visit_expr_call(self, node);
2586 return;
2587 }
2588 }
2589
2590 let snippet = self.editor.format_expr_call(node);
2592 let location = self.editor.span_to_location(node.span());
2593
2594 let preceding_comment = if self.include_comments {
2596 extract_preceding_comment(&self.editor.content, location.line)
2597 } else {
2598 None
2599 };
2600
2601 let preceding_comment = if self.include_comments {
2603 extract_preceding_comment(&self.editor.content, location.line)
2604 } else {
2605 None
2606 };
2607
2608 self.results.push(InspectResult {
2609 file_path: String::new(), node_type: "function-call".to_string(),
2611 identifier: func_name,
2612 location,
2613 snippet,
2614 preceding_comment,
2615 });
2616
2617 syn::visit::visit_expr_call(self, node);
2619 }
2620 }
2621
2622 let mut visitor = FunctionCallVisitor {
2623 results: &mut results,
2624 name_filter,
2625 editor: self,
2626 include_comments,
2627 };
2628
2629 for item in &self.syntax_tree.items {
2631 syn::visit::visit_item(&mut visitor, item);
2632 }
2633 }
2634 "trait-method" => {
2635 struct TraitMethodVisitor<'a> {
2637 results: &'a mut Vec<InspectResult>,
2638 name_filter: Option<&'a str>,
2639 editor: &'a RustEditor,
2640 include_comments: bool,
2641 current_trait_name: Option<String>,
2642 }
2643
2644 impl<'ast, 'a> Visit<'ast> for TraitMethodVisitor<'a> {
2645 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
2646 let prev_trait_name = self.current_trait_name.clone();
2648 self.current_trait_name = Some(node.ident.to_string());
2649
2650 syn::visit::visit_item_trait(self, node);
2651
2652 self.current_trait_name = prev_trait_name;
2653 }
2654
2655 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
2656 let method_name = node.sig.ident.to_string();
2657
2658 let identifier = if let Some(ref trait_name) = self.current_trait_name {
2660 format!("{}::{}", trait_name, method_name)
2661 } else {
2662 method_name.clone()
2663 };
2664
2665 if let Some(filter) = self.name_filter {
2667 if !identifier.contains(filter) && method_name != filter {
2669 syn::visit::visit_trait_item_fn(self, node);
2670 return;
2671 }
2672 }
2673
2674 let snippet = self.editor.format_trait_item_fn(node);
2676 let location = self.editor.span_to_location(node.span());
2677
2678 let preceding_comment = if self.include_comments {
2680 extract_preceding_comment(&self.editor.content, location.line)
2681 } else {
2682 None
2683 };
2684
2685 self.results.push(InspectResult {
2686 file_path: String::new(),
2687 node_type: "trait-method".to_string(),
2688 identifier,
2689 location,
2690 snippet,
2691 preceding_comment,
2692 });
2693
2694 syn::visit::visit_trait_item_fn(self, node);
2695 }
2696 }
2697
2698 let mut visitor = TraitMethodVisitor {
2699 results: &mut results,
2700 name_filter,
2701 editor: self,
2702 include_comments,
2703 current_trait_name: None,
2704 };
2705
2706 for item in &self.syntax_tree.items {
2707 syn::visit::visit_item(&mut visitor, item);
2708 }
2709 }
2710 "method-call" => {
2711 struct MethodCallVisitor<'a> {
2713 results: &'a mut Vec<InspectResult>,
2714 name_filter: Option<&'a str>,
2715 editor: &'a RustEditor,
2716 include_comments: bool,
2717 }
2718
2719 impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
2720 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
2721 let method_name = node.method.to_string();
2723
2724 if let Some(filter) = self.name_filter {
2726 if method_name != filter {
2727 syn::visit::visit_expr_method_call(self, node);
2728 return;
2729 }
2730 }
2731
2732 let snippet = self.editor.format_expr_method_call(node);
2734 let location = self.editor.span_to_location(node.span());
2735
2736 let preceding_comment = if self.include_comments {
2738 extract_preceding_comment(&self.editor.content, location.line)
2739 } else {
2740 None
2741 };
2742
2743 self.results.push(InspectResult {
2744 file_path: String::new(), node_type: "method-call".to_string(),
2746 identifier: method_name,
2747 location,
2748 snippet,
2749 preceding_comment,
2750 });
2751
2752 syn::visit::visit_expr_method_call(self, node);
2754 }
2755 }
2756
2757 let mut visitor = MethodCallVisitor {
2758 results: &mut results,
2759 name_filter,
2760 editor: self,
2761 include_comments,
2762 };
2763
2764 for item in &self.syntax_tree.items {
2766 syn::visit::visit_item(&mut visitor, item);
2767 }
2768 }
2769 "identifier" => {
2770 struct IdentifierVisitor<'a> {
2772 results: &'a mut Vec<InspectResult>,
2773 name_filter: Option<&'a str>,
2774 editor: &'a RustEditor,
2775 include_comments: bool,
2776 }
2777
2778 impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2779 fn visit_ident(&mut self, node: &'ast syn::Ident) {
2780 let ident_name = node.to_string();
2782
2783 if let Some(filter) = self.name_filter {
2785 if ident_name != filter {
2786 syn::visit::visit_ident(self, node);
2787 return;
2788 }
2789 }
2790
2791 let snippet = self.editor.format_ident(node);
2793 let location = self.editor.span_to_location(node.span());
2794
2795 let preceding_comment = if self.include_comments {
2797 extract_preceding_comment(&self.editor.content, location.line)
2798 } else {
2799 None
2800 };
2801
2802 self.results.push(InspectResult {
2803 file_path: String::new(), node_type: "identifier".to_string(),
2805 identifier: ident_name,
2806 location,
2807 snippet,
2808 preceding_comment,
2809 });
2810
2811 syn::visit::visit_ident(self, node);
2813 }
2814 }
2815
2816 let mut visitor = IdentifierVisitor {
2817 results: &mut results,
2818 name_filter,
2819 editor: self,
2820 include_comments,
2821 };
2822
2823 for item in &self.syntax_tree.items {
2825 syn::visit::visit_item(&mut visitor, item);
2826 }
2827 }
2828 "type-ref" => {
2829 struct TypeRefVisitor<'a> {
2831 results: &'a mut Vec<InspectResult>,
2832 name_filter: Option<&'a str>,
2833 editor: &'a RustEditor,
2834 include_comments: bool,
2835 }
2836
2837 impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2838 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2839 let type_name = node.path.segments.last()
2841 .map(|seg| seg.ident.to_string())
2842 .unwrap_or_default();
2843
2844 if let Some(filter) = self.name_filter {
2846 if type_name != filter {
2847 syn::visit::visit_type_path(self, node);
2848 return;
2849 }
2850 }
2851
2852 let snippet = self.editor.format_type_path(node);
2854 let location = self.editor.span_to_location(node.span());
2855
2856 let preceding_comment = if self.include_comments {
2858 extract_preceding_comment(&self.editor.content, location.line)
2859 } else {
2860 None
2861 };
2862
2863 let path = &node.path;
2865 let path_str = quote::quote!(#path).to_string();
2866
2867 self.results.push(InspectResult {
2868 file_path: String::new(), node_type: "type-ref".to_string(),
2870 identifier: path_str.replace(" ", ""),
2871 location,
2872 snippet,
2873 preceding_comment,
2874 });
2875
2876 syn::visit::visit_type_path(self, node);
2878 }
2879 }
2880
2881 let mut visitor = TypeRefVisitor {
2882 results: &mut results,
2883 name_filter,
2884 editor: self,
2885 include_comments,
2886 };
2887
2888 for item in &self.syntax_tree.items {
2890 syn::visit::visit_item(&mut visitor, item);
2891 }
2892 }
2893 "macro-call" => {
2894 struct MacroCallVisitor<'a> {
2896 results: &'a mut Vec<InspectResult>,
2897 name_filter: Option<&'a str>,
2898 editor: &'a RustEditor,
2899 include_comments: bool,
2900 }
2901
2902 impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2903 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2904 let macro_name = node.mac.path.segments.last()
2906 .map(|seg| seg.ident.to_string())
2907 .unwrap_or_default();
2908
2909 if let Some(filter) = self.name_filter {
2911 if macro_name != filter {
2912 syn::visit::visit_expr_macro(self, node);
2913 return;
2914 }
2915 }
2916
2917 let snippet = self.editor.format_expr_macro(node);
2919 let location = self.editor.span_to_location(node.span());
2920
2921 let preceding_comment = if self.include_comments {
2923 extract_preceding_comment(&self.editor.content, location.line)
2924 } else {
2925 None
2926 };
2927
2928 self.results.push(InspectResult {
2929 file_path: String::new(), node_type: "macro-call".to_string(),
2931 identifier: macro_name,
2932 location,
2933 snippet,
2934 preceding_comment,
2935 });
2936
2937 syn::visit::visit_expr_macro(self, node);
2939 }
2940
2941 fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2942 if let syn::Stmt::Macro(macro_stmt) = node {
2944 let macro_name = macro_stmt.mac.path.segments.last()
2945 .map(|seg| seg.ident.to_string())
2946 .unwrap_or_default();
2947
2948 if let Some(filter) = self.name_filter {
2950 if macro_name != filter {
2951 syn::visit::visit_stmt(self, node);
2952 return;
2953 }
2954 }
2955
2956 let snippet = self.editor.format_stmt_macro(macro_stmt);
2958 let location = self.editor.span_to_location(macro_stmt.span());
2959
2960 let preceding_comment = if self.include_comments {
2962 extract_preceding_comment(&self.editor.content, location.line)
2963 } else {
2964 None
2965 };
2966
2967 self.results.push(InspectResult {
2968 file_path: String::new(), node_type: "macro-call".to_string(),
2970 identifier: macro_name,
2971 location,
2972 snippet,
2973 preceding_comment,
2974 });
2975 }
2976
2977 syn::visit::visit_stmt(self, node);
2979 }
2980 }
2981
2982 let mut visitor = MacroCallVisitor {
2983 results: &mut results,
2984 name_filter,
2985 editor: self,
2986 include_comments,
2987 };
2988
2989 for item in &self.syntax_tree.items {
2991 syn::visit::visit_item(&mut visitor, item);
2992 }
2993 }
2994 "struct" => {
2995 struct StructDefVisitor<'a> {
2997 results: &'a mut Vec<InspectResult>,
2998 name_filter: Option<&'a str>,
2999 editor: &'a RustEditor,
3000 include_comments: bool,
3001 }
3002
3003 impl<'ast, 'a> Visit<'ast> for StructDefVisitor<'a> {
3004 fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
3005 let struct_name = node.ident.to_string();
3006
3007 if let Some(filter) = self.name_filter {
3009 if struct_name != filter {
3010 syn::visit::visit_item_struct(self, node);
3011 return;
3012 }
3013 }
3014
3015 let snippet = self.editor.format_item_struct(node);
3017 let location = self.editor.span_to_location(node.span());
3018
3019 let preceding_comment = if self.include_comments {
3021 extract_preceding_comment(&self.editor.content, location.line)
3022 } else {
3023 None
3024 };
3025
3026 self.results.push(InspectResult {
3027 file_path: String::new(),
3028 node_type: "struct".to_string(),
3029 identifier: struct_name,
3030 location,
3031 snippet,
3032 preceding_comment,
3033 });
3034
3035 syn::visit::visit_item_struct(self, node);
3036 }
3037 }
3038
3039 let mut visitor = StructDefVisitor {
3040 results: &mut results,
3041 name_filter,
3042 editor: self,
3043 include_comments,
3044 };
3045
3046 for item in &self.syntax_tree.items {
3047 syn::visit::visit_item(&mut visitor, item);
3048 }
3049 }
3050 "enum" => {
3051 struct EnumDefVisitor<'a> {
3058 results: &'a mut Vec<InspectResult>,
3059 name_filter: Option<&'a str>,
3060 variant_filter: Option<&'a str>,
3061 editor: &'a RustEditor,
3062 include_comments: bool,
3063 }
3064
3065 impl<'ast, 'a> Visit<'ast> for EnumDefVisitor<'a> {
3066 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
3067 let enum_name = node.ident.to_string();
3068
3069 let (enum_name_filter, implicit_variant_filter) = if let Some(filter) = self.name_filter {
3071 if filter.contains("::") {
3072 let parts: Vec<&str> = filter.split("::").collect();
3074 if parts.len() == 2 {
3075 if parts[0] == "*" {
3076 (None, Some(parts[1]))
3078 } else {
3079 (Some(parts[0]), Some(parts[1]))
3081 }
3082 } else {
3083 (Some(filter), None)
3085 }
3086 } else {
3087 (Some(filter), None)
3089 }
3090 } else {
3091 (None, None)
3092 };
3093
3094 let effective_variant_filter = self.variant_filter.or(implicit_variant_filter);
3096
3097 if let Some(filter) = enum_name_filter {
3099 if enum_name != filter {
3100 syn::visit::visit_item_enum(self, node);
3101 return;
3102 }
3103 }
3104
3105 if let Some(variant_name) = effective_variant_filter {
3107 let has_variant = node.variants.iter().any(|v| v.ident.to_string() == variant_name);
3108 if !has_variant {
3109 syn::visit::visit_item_enum(self, node);
3110 return;
3111 }
3112 }
3113
3114 let snippet = if let Some(variant_name) = effective_variant_filter {
3116 self.editor.format_item_enum_variant_only(node, variant_name)
3118 } else {
3119 self.editor.format_item_enum(node)
3120 };
3121
3122 let location = self.editor.span_to_location(node.span());
3123
3124 let preceding_comment = if self.include_comments {
3126 extract_preceding_comment(&self.editor.content, location.line)
3127 } else {
3128 None
3129 };
3130
3131 self.results.push(InspectResult {
3132 file_path: String::new(),
3133 node_type: "enum".to_string(),
3134 identifier: enum_name,
3135 location,
3136 snippet,
3137 preceding_comment,
3138 });
3139
3140 syn::visit::visit_item_enum(self, node);
3141 }
3142 }
3143
3144 let mut visitor = EnumDefVisitor {
3145 results: &mut results,
3146 name_filter,
3147 variant_filter,
3148 editor: self,
3149 include_comments,
3150 };
3151
3152 for item in &self.syntax_tree.items {
3153 syn::visit::visit_item(&mut visitor, item);
3154 }
3155 }
3156 "function" => {
3157 struct FunctionDefVisitor<'a> {
3159 results: &'a mut Vec<InspectResult>,
3160 name_filter: Option<&'a str>,
3161 editor: &'a RustEditor,
3162 include_comments: bool,
3163 }
3164
3165 impl<'ast, 'a> Visit<'ast> for FunctionDefVisitor<'a> {
3166 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3167 let fn_name = node.sig.ident.to_string();
3168
3169 if let Some(filter) = self.name_filter {
3171 if fn_name != filter {
3172 syn::visit::visit_item_fn(self, node);
3173 return;
3174 }
3175 }
3176
3177 let snippet = self.editor.format_item_fn(node);
3179 let location = self.editor.span_to_location(node.span());
3180
3181 let preceding_comment = if self.include_comments {
3183 extract_preceding_comment(&self.editor.content, location.line)
3184 } else {
3185 None
3186 };
3187
3188 self.results.push(InspectResult {
3189 file_path: String::new(),
3190 node_type: "function".to_string(),
3191 identifier: fn_name,
3192 location,
3193 snippet,
3194 preceding_comment,
3195 });
3196
3197 syn::visit::visit_item_fn(self, node);
3198 }
3199 }
3200
3201 let mut visitor = FunctionDefVisitor {
3202 results: &mut results,
3203 name_filter,
3204 editor: self,
3205 include_comments,
3206 };
3207
3208 for item in &self.syntax_tree.items {
3209 syn::visit::visit_item(&mut visitor, item);
3210 }
3211 }
3212 "impl-method" => {
3213 struct ImplMethodVisitor<'a> {
3215 results: &'a mut Vec<InspectResult>,
3216 name_filter: Option<&'a str>,
3217 editor: &'a RustEditor,
3218 include_comments: bool,
3219 current_impl_type: Option<String>,
3220 }
3221
3222 impl<'ast, 'a> Visit<'ast> for ImplMethodVisitor<'a> {
3223 fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
3224 let impl_type = if let syn::Type::Path(type_path) = &*node.self_ty {
3226 type_path.path.segments.last()
3227 .map(|seg| seg.ident.to_string())
3228 } else {
3229 None
3230 };
3231
3232 let prev_impl_type = self.current_impl_type.clone();
3233 self.current_impl_type = impl_type;
3234
3235 syn::visit::visit_item_impl(self, node);
3236
3237 self.current_impl_type = prev_impl_type;
3238 }
3239
3240 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
3241 let method_name = node.sig.ident.to_string();
3242
3243 let identifier = if let Some(ref impl_type) = self.current_impl_type {
3245 format!("{}::{}", impl_type, method_name)
3246 } else {
3247 method_name.clone()
3248 };
3249
3250 if let Some(filter) = self.name_filter {
3252 if !identifier.contains(filter) && method_name != filter {
3254 syn::visit::visit_impl_item_fn(self, node);
3255 return;
3256 }
3257 }
3258
3259 let snippet = self.editor.format_impl_item_fn(node);
3261 let location = self.editor.span_to_location(node.span());
3262
3263 let preceding_comment = if self.include_comments {
3265 extract_preceding_comment(&self.editor.content, location.line)
3266 } else {
3267 None
3268 };
3269
3270 self.results.push(InspectResult {
3271 file_path: String::new(),
3272 node_type: "impl-method".to_string(),
3273 identifier,
3274 location,
3275 snippet,
3276 preceding_comment,
3277 });
3278
3279 syn::visit::visit_impl_item_fn(self, node);
3280 }
3281 }
3282
3283 let mut visitor = ImplMethodVisitor {
3284 results: &mut results,
3285 name_filter,
3286 editor: self,
3287 include_comments,
3288 current_impl_type: None,
3289 };
3290
3291 for item in &self.syntax_tree.items {
3292 syn::visit::visit_item(&mut visitor, item);
3293 }
3294 }
3295 "trait" => {
3296 struct TraitDefVisitor<'a> {
3298 results: &'a mut Vec<InspectResult>,
3299 name_filter: Option<&'a str>,
3300 editor: &'a RustEditor,
3301 include_comments: bool,
3302 }
3303
3304 impl<'ast, 'a> Visit<'ast> for TraitDefVisitor<'a> {
3305 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
3306 let trait_name = node.ident.to_string();
3307
3308 if let Some(filter) = self.name_filter {
3310 if trait_name != filter {
3311 syn::visit::visit_item_trait(self, node);
3312 return;
3313 }
3314 }
3315
3316 let snippet = self.editor.format_item_trait(node);
3318 let location = self.editor.span_to_location(node.span());
3319
3320 let preceding_comment = if self.include_comments {
3322 extract_preceding_comment(&self.editor.content, location.line)
3323 } else {
3324 None
3325 };
3326
3327 self.results.push(InspectResult {
3328 file_path: String::new(),
3329 node_type: "trait".to_string(),
3330 identifier: trait_name,
3331 location,
3332 snippet,
3333 preceding_comment,
3334 });
3335
3336 syn::visit::visit_item_trait(self, node);
3337 }
3338 }
3339
3340 let mut visitor = TraitDefVisitor {
3341 results: &mut results,
3342 name_filter,
3343 editor: self,
3344 include_comments,
3345 };
3346
3347 for item in &self.syntax_tree.items {
3348 syn::visit::visit_item(&mut visitor, item);
3349 }
3350 }
3351 "const" => {
3352 struct ConstDefVisitor<'a> {
3354 results: &'a mut Vec<InspectResult>,
3355 name_filter: Option<&'a str>,
3356 editor: &'a RustEditor,
3357 include_comments: bool,
3358 }
3359
3360 impl<'ast, 'a> Visit<'ast> for ConstDefVisitor<'a> {
3361 fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
3362 let const_name = node.ident.to_string();
3363
3364 if let Some(filter) = self.name_filter {
3366 if const_name != filter {
3367 syn::visit::visit_item_const(self, node);
3368 return;
3369 }
3370 }
3371
3372 let snippet = self.editor.format_item_const(node);
3374 let location = self.editor.span_to_location(node.span());
3375
3376 let preceding_comment = if self.include_comments {
3378 extract_preceding_comment(&self.editor.content, location.line)
3379 } else {
3380 None
3381 };
3382
3383 self.results.push(InspectResult {
3384 file_path: String::new(),
3385 node_type: "const".to_string(),
3386 identifier: const_name,
3387 location,
3388 snippet,
3389 preceding_comment,
3390 });
3391
3392 syn::visit::visit_item_const(self, node);
3393 }
3394 }
3395
3396 let mut visitor = ConstDefVisitor {
3397 results: &mut results,
3398 name_filter,
3399 editor: self,
3400 include_comments,
3401 };
3402
3403 for item in &self.syntax_tree.items {
3404 syn::visit::visit_item(&mut visitor, item);
3405 }
3406 }
3407 "static" => {
3408 struct StaticDefVisitor<'a> {
3410 results: &'a mut Vec<InspectResult>,
3411 name_filter: Option<&'a str>,
3412 editor: &'a RustEditor,
3413 include_comments: bool,
3414 }
3415
3416 impl<'ast, 'a> Visit<'ast> for StaticDefVisitor<'a> {
3417 fn visit_item_static(&mut self, node: &'ast syn::ItemStatic) {
3418 let static_name = node.ident.to_string();
3419
3420 if let Some(filter) = self.name_filter {
3422 if static_name != filter {
3423 syn::visit::visit_item_static(self, node);
3424 return;
3425 }
3426 }
3427
3428 let snippet = self.editor.format_item_static(node);
3430 let location = self.editor.span_to_location(node.span());
3431
3432 let preceding_comment = if self.include_comments {
3434 extract_preceding_comment(&self.editor.content, location.line)
3435 } else {
3436 None
3437 };
3438
3439 self.results.push(InspectResult {
3440 file_path: String::new(),
3441 node_type: "static".to_string(),
3442 identifier: static_name,
3443 location,
3444 snippet,
3445 preceding_comment,
3446 });
3447
3448 syn::visit::visit_item_static(self, node);
3449 }
3450 }
3451
3452 let mut visitor = StaticDefVisitor {
3453 results: &mut results,
3454 name_filter,
3455 editor: self,
3456 include_comments,
3457 };
3458
3459 for item in &self.syntax_tree.items {
3460 syn::visit::visit_item(&mut visitor, item);
3461 }
3462 }
3463 "type-alias" => {
3464 struct TypeAliasVisitor<'a> {
3466 results: &'a mut Vec<InspectResult>,
3467 name_filter: Option<&'a str>,
3468 editor: &'a RustEditor,
3469 include_comments: bool,
3470 }
3471
3472 impl<'ast, 'a> Visit<'ast> for TypeAliasVisitor<'a> {
3473 fn visit_item_type(&mut self, node: &'ast syn::ItemType) {
3474 let type_name = node.ident.to_string();
3475
3476 if let Some(filter) = self.name_filter {
3478 if type_name != filter {
3479 syn::visit::visit_item_type(self, node);
3480 return;
3481 }
3482 }
3483
3484 let snippet = self.editor.format_item_type(node);
3486 let location = self.editor.span_to_location(node.span());
3487
3488 let preceding_comment = if self.include_comments {
3490 extract_preceding_comment(&self.editor.content, location.line)
3491 } else {
3492 None
3493 };
3494
3495 self.results.push(InspectResult {
3496 file_path: String::new(),
3497 node_type: "type-alias".to_string(),
3498 identifier: type_name,
3499 location,
3500 snippet,
3501 preceding_comment,
3502 });
3503
3504 syn::visit::visit_item_type(self, node);
3505 }
3506 }
3507
3508 let mut visitor = TypeAliasVisitor {
3509 results: &mut results,
3510 name_filter,
3511 editor: self,
3512 include_comments,
3513 };
3514
3515 for item in &self.syntax_tree.items {
3516 syn::visit::visit_item(&mut visitor, item);
3517 }
3518 }
3519 "mod" => {
3520 struct ModDefVisitor<'a> {
3522 results: &'a mut Vec<InspectResult>,
3523 name_filter: Option<&'a str>,
3524 editor: &'a RustEditor,
3525 include_comments: bool,
3526 }
3527
3528 impl<'ast, 'a> Visit<'ast> for ModDefVisitor<'a> {
3529 fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
3530 let mod_name = node.ident.to_string();
3531
3532 if let Some(filter) = self.name_filter {
3534 if mod_name != filter {
3535 syn::visit::visit_item_mod(self, node);
3536 return;
3537 }
3538 }
3539
3540 let snippet = self.editor.format_item_mod(node);
3542 let location = self.editor.span_to_location(node.span());
3543
3544 let preceding_comment = if self.include_comments {
3546 extract_preceding_comment(&self.editor.content, location.line)
3547 } else {
3548 None
3549 };
3550
3551 self.results.push(InspectResult {
3552 file_path: String::new(),
3553 node_type: "mod".to_string(),
3554 identifier: mod_name,
3555 location,
3556 snippet,
3557 preceding_comment,
3558 });
3559
3560 syn::visit::visit_item_mod(self, node);
3561 }
3562 }
3563
3564 let mut visitor = ModDefVisitor {
3565 results: &mut results,
3566 name_filter,
3567 editor: self,
3568 include_comments,
3569 };
3570
3571 for item in &self.syntax_tree.items {
3572 syn::visit::visit_item(&mut visitor, item);
3573 }
3574 }
3575 _ => anyhow::bail!("Unsupported node type: {}", node_type),
3576 }
3577
3578 Ok(results)
3579 }
3580
3581 fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
3583 let start = self.span_to_byte_offset(expr.span().start());
3585 let end = self.span_to_byte_offset(expr.span().end());
3586
3587 let original = &self.content[start..end];
3589
3590 original.split_whitespace().collect::<Vec<_>>().join(" ")
3592 }
3593
3594 fn format_match_arm(&self, arm: &syn::Arm) -> String {
3596 let start = self.span_to_byte_offset(arm.span().start());
3598 let end = self.span_to_byte_offset(arm.span().end());
3599
3600 let original = &self.content[start..end];
3602
3603 original.split_whitespace().collect::<Vec<_>>().join(" ")
3605 }
3606
3607 fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
3609 let start = self.span_to_byte_offset(expr.span().start());
3611 let end = self.span_to_byte_offset(expr.span().end());
3612
3613 let original = &self.content[start..end];
3615
3616 original.split_whitespace().collect::<Vec<_>>().join(" ")
3618 }
3619
3620 fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
3622 let start = self.span_to_byte_offset(expr.span().start());
3624 let end = self.span_to_byte_offset(expr.span().end());
3625
3626 let original = &self.content[start..end];
3628
3629 original.split_whitespace().collect::<Vec<_>>().join(" ")
3631 }
3632
3633 fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
3635 let start = self.span_to_byte_offset(expr.span().start());
3637 let end = self.span_to_byte_offset(expr.span().end());
3638
3639 let original = &self.content[start..end];
3641
3642 original.split_whitespace().collect::<Vec<_>>().join(" ")
3644 }
3645
3646 fn format_ident(&self, ident: &syn::Ident) -> String {
3648 ident.to_string()
3649 }
3650
3651 fn format_type_path(&self, ty: &syn::TypePath) -> String {
3653 let start = self.span_to_byte_offset(ty.span().start());
3655 let end = self.span_to_byte_offset(ty.span().end());
3656
3657 let original = &self.content[start..end];
3659
3660 original.split_whitespace().collect::<Vec<_>>().join(" ")
3662 }
3663
3664 fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
3666 let start = self.span_to_byte_offset(expr.span().start());
3668 let end = self.span_to_byte_offset(expr.span().end());
3669
3670 let original = &self.content[start..end];
3672
3673 original.split_whitespace().collect::<Vec<_>>().join(" ")
3675 }
3676
3677 fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
3679 let start = self.span_to_byte_offset(stmt.span().start());
3681 let end = self.span_to_byte_offset(stmt.span().end());
3682
3683 let original = &self.content[start..end];
3685
3686 original.split_whitespace().collect::<Vec<_>>().join(" ")
3688 }
3689
3690 fn format_item_struct(&self, item: &syn::ItemStruct) -> String {
3692 let start = self.span_to_byte_offset(item.span().start());
3693 let end = self.span_to_byte_offset(item.span().end());
3694 let original = &self.content[start..end];
3695 original.to_string()
3696 }
3697
3698 fn format_item_enum(&self, item: &syn::ItemEnum) -> String {
3700 let start = self.span_to_byte_offset(item.span().start());
3701 let end = self.span_to_byte_offset(item.span().end());
3702 let original = &self.content[start..end];
3703 original.to_string()
3704 }
3705
3706 fn format_item_enum_variant_only(&self, item: &syn::ItemEnum, variant_name: &str) -> String {
3708 let variant = item.variants.iter()
3710 .find(|v| v.ident.to_string() == variant_name);
3711
3712 if let Some(variant) = variant {
3713 let enum_start = self.span_to_byte_offset(item.span().start());
3715 let variants_start = if !item.variants.is_empty() {
3716 self.span_to_byte_offset(item.variants.first().unwrap().span().start())
3717 } else {
3718 self.span_to_byte_offset(item.span().end())
3719 };
3720
3721 let header = &self.content[enum_start..variants_start].trim_end();
3723
3724 let variant_start = self.span_to_byte_offset(variant.span().start());
3726 let variant_end = self.span_to_byte_offset(variant.span().end());
3727 let variant_source = &self.content[variant_start..variant_end];
3728
3729 format!("{}\n {},\n // ... {} other variant{}\n}}",
3731 header,
3732 variant_source,
3733 item.variants.len() - 1,
3734 if item.variants.len() - 1 == 1 { "" } else { "s" }
3735 )
3736 } else {
3737 self.format_item_enum(item)
3739 }
3740 }
3741
3742 fn format_item_fn(&self, item: &syn::ItemFn) -> String {
3744 let start = self.span_to_byte_offset(item.span().start());
3745 let end = self.span_to_byte_offset(item.span().end());
3746 let original = &self.content[start..end];
3747 original.to_string()
3748 }
3749
3750 fn format_impl_item_fn(&self, item: &syn::ImplItemFn) -> String {
3752 let start = self.span_to_byte_offset(item.span().start());
3753 let end = self.span_to_byte_offset(item.span().end());
3754 let original = &self.content[start..end];
3755 original.to_string()
3756 }
3757
3758 fn format_trait_item_fn(&self, item: &syn::TraitItemFn) -> String {
3760 let start = self.span_to_byte_offset(item.span().start());
3761 let end = self.span_to_byte_offset(item.span().end());
3762 let original = &self.content[start..end];
3763 original.to_string()
3764 }
3765
3766 fn format_item_trait(&self, item: &syn::ItemTrait) -> String {
3768 let start = self.span_to_byte_offset(item.span().start());
3769 let end = self.span_to_byte_offset(item.span().end());
3770 let original = &self.content[start..end];
3771 original.to_string()
3772 }
3773
3774 fn format_item_const(&self, item: &syn::ItemConst) -> String {
3776 let start = self.span_to_byte_offset(item.span().start());
3777 let end = self.span_to_byte_offset(item.span().end());
3778 let original = &self.content[start..end];
3779 original.to_string()
3780 }
3781
3782 fn format_item_static(&self, item: &syn::ItemStatic) -> String {
3784 let start = self.span_to_byte_offset(item.span().start());
3785 let end = self.span_to_byte_offset(item.span().end());
3786 let original = &self.content[start..end];
3787 original.to_string()
3788 }
3789
3790 fn format_item_type(&self, item: &syn::ItemType) -> 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_mod(&self, item: &syn::ItemMod) -> 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 #[allow(dead_code)]
3808 pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
3809 for (index, item) in self.syntax_tree.items.iter().enumerate() {
3810 match (node_type, item) {
3811 ("struct", Item::Struct(s)) if s.ident == name => {
3812 return Ok(index);
3813 }
3814 ("enum", Item::Enum(e)) if e.ident == name => {
3815 return Ok(index);
3816 }
3817 ("fn", Item::Fn(f)) if f.sig.ident == name => {
3818 return Ok(index);
3819 }
3820 ("impl", Item::Impl(impl_block)) => {
3821 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
3823 if let Some(segment) = type_path.path.segments.last() {
3824 if segment.ident == name {
3825 return Ok(index);
3826 }
3827 }
3828 }
3829 }
3830 _ => {}
3831 }
3832 }
3833
3834 anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
3835 }
3836
3837 #[allow(dead_code)]
3839 pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
3840 if index >= self.syntax_tree.items.len() {
3841 anyhow::bail!("Index {} out of bounds", index);
3842 }
3843
3844 self.syntax_tree.items[index] = new_item;
3846
3847 self.content = prettyplease::unparse(&self.syntax_tree);
3849
3850 self.line_offsets = Self::compute_line_offsets(&self.content);
3852
3853 Ok(())
3854 }
3855
3856 fn span_to_location(&self, span: Span) -> NodeLocation {
3857 let start = span.start();
3858 let end = span.end();
3859
3860 NodeLocation {
3861 line: start.line,
3862 column: start.column,
3863 end_line: end.line,
3864 end_column: end.column,
3865 }
3866 }
3867
3868 pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
3870 use crate::operations::{InspectResult, TransformAction};
3871
3872 let matches = self.inspect(Some(&op.node_type), op.name_filter.as_deref(), None, false)?;
3874
3875 let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
3877 matches.into_iter()
3878 .filter(|m| m.snippet.contains(content_filter))
3879 .collect()
3880 } else {
3881 matches
3882 };
3883
3884 if filtered_matches.is_empty() {
3885 return Ok(ModificationResult {
3886 changed: false,
3887 modified_nodes: vec![],
3888 });
3889 }
3890
3891 let mut sorted_matches = filtered_matches;
3894 sorted_matches.sort_by(|a, b| {
3895 b.location.line.cmp(&a.location.line)
3896 .then(b.location.column.cmp(&a.location.column))
3897 });
3898
3899 let mut modified_nodes = Vec::new();
3900
3901 for match_result in &sorted_matches {
3902 let backup_node = BackupNode {
3904 node_type: match_result.node_type.clone(),
3905 identifier: match_result.identifier.clone(),
3906 original_content: match_result.snippet.clone(),
3907 location: match_result.location.clone(),
3908 };
3909
3910 let start_offset = self.line_column_to_byte_offset(
3912 match_result.location.line,
3913 match_result.location.column
3914 )?;
3915 let end_offset = self.line_column_to_byte_offset(
3916 match_result.location.end_line,
3917 match_result.location.end_column
3918 )?;
3919
3920 let original_text = &self.content[start_offset..end_offset];
3922
3923 let replacement = match &op.action {
3925 TransformAction::Comment => {
3926 format!("// {}", original_text.replace("\n", "\n// "))
3928 }
3929 TransformAction::Remove => {
3930 String::new()
3932 }
3933 TransformAction::Replace { with } => {
3934 with.clone()
3936 }
3937 };
3938
3939 self.content.replace_range(start_offset..end_offset, &replacement);
3941
3942 self.line_offsets = Self::compute_line_offsets(&self.content);
3944
3945 modified_nodes.push(backup_node);
3946 }
3947
3948 if !modified_nodes.is_empty() {
3950 }
3954
3955 Ok(ModificationResult {
3956 changed: !modified_nodes.is_empty(),
3957 modified_nodes,
3958 })
3959 }
3960
3961 pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
3963 use crate::operations::EditMode;
3964
3965 let path_resolver = if let Some(enum_path) = &op.enum_path {
3967 let mut resolver = PathResolver::new(enum_path)
3968 .ok_or_else(|| anyhow::anyhow!("Invalid enum path: {}", enum_path))?;
3969
3970 resolver.scan_file(&self.syntax_tree);
3972 Some(resolver)
3973 } else {
3974 None
3975 };
3976
3977 match op.edit_mode {
3978 EditMode::Surgical => {
3979 use syn::visit::Visit;
3981 use crate::surgical::Replacement;
3982
3983 let mut collector = EnumVariantReplacementCollector {
3984 enum_name: op.enum_name.clone(),
3985 old_variant: op.old_variant.clone(),
3986 new_variant: op.new_variant.clone(),
3987 path_resolver,
3988 replacements: Vec::new(),
3989 };
3990
3991 collector.visit_file(&self.syntax_tree);
3992
3993 if collector.replacements.is_empty() {
3994 return Ok(ModificationResult {
3995 changed: false,
3996 modified_nodes: vec![],
3997 });
3998 }
3999
4000 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
4002
4003 self.line_offsets = Self::compute_line_offsets(&self.content);
4005
4006 self.syntax_tree = syn::parse_str(&self.content)
4008 .context("Failed to re-parse after surgical edit")?;
4009
4010 let backup_node = BackupNode {
4011 node_type: "EnumVariantRename".to_string(),
4012 identifier: format!("{}::{} -> {} (surgical)", op.enum_name, op.old_variant, op.new_variant),
4013 original_content: format!("Renamed {} to {} in enum {} (surgical mode)", op.old_variant, op.new_variant, op.enum_name),
4014 location: NodeLocation {
4015 line: 1,
4016 column: 0,
4017 end_line: 1,
4018 end_column: 0,
4019 },
4020 };
4021
4022 Ok(ModificationResult {
4023 changed: true,
4024 modified_nodes: vec![backup_node],
4025 })
4026 }
4027 EditMode::Reformat => {
4028 let mut renamer = EnumVariantRenamer {
4030 enum_name: op.enum_name.clone(),
4031 old_variant: op.old_variant.clone(),
4032 new_variant: op.new_variant.clone(),
4033 path_resolver,
4034 modified: false,
4035 };
4036
4037 renamer.visit_file_mut(&mut self.syntax_tree);
4039
4040 if !renamer.modified {
4041 return Ok(ModificationResult {
4042 changed: false,
4043 modified_nodes: vec![],
4044 });
4045 }
4046
4047 self.content = prettyplease::unparse(&self.syntax_tree);
4049
4050 self.line_offsets = Self::compute_line_offsets(&self.content);
4052
4053 let backup_node = BackupNode {
4055 node_type: "EnumVariantRename".to_string(),
4056 identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
4057 original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
4058 location: NodeLocation {
4059 line: 1,
4060 column: 0,
4061 end_line: 1,
4062 end_column: 0,
4063 },
4064 };
4065
4066 Ok(ModificationResult {
4067 changed: true,
4068 modified_nodes: vec![backup_node],
4069 })
4070 }
4071 }
4072 }
4073
4074 pub(crate) fn rename_function(&mut self, op: &crate::operations::RenameFunctionOp) -> Result<ModificationResult> {
4076 use crate::operations::EditMode;
4077
4078 let path_resolver = if let Some(function_path) = &op.function_path {
4080 let mut resolver = PathResolver::new(function_path)
4081 .ok_or_else(|| anyhow::anyhow!("Invalid function path: {}", function_path))?;
4082
4083 resolver.scan_file(&self.syntax_tree);
4085 Some(resolver)
4086 } else {
4087 None
4088 };
4089
4090 match op.edit_mode {
4091 EditMode::Surgical => {
4092 use syn::visit::Visit;
4094
4095 let mut collector = FunctionReplacementCollector {
4096 old_name: op.old_name.clone(),
4097 new_name: op.new_name.clone(),
4098 path_resolver,
4099 replacements: Vec::new(),
4100 };
4101
4102 collector.visit_file(&self.syntax_tree);
4103
4104 if collector.replacements.is_empty() {
4105 return Ok(ModificationResult {
4106 changed: false,
4107 modified_nodes: vec![],
4108 });
4109 }
4110
4111 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
4113
4114 self.line_offsets = Self::compute_line_offsets(&self.content);
4116
4117 self.syntax_tree = syn::parse_str(&self.content)
4119 .context("Failed to re-parse after surgical edit")?;
4120
4121 let backup_node = BackupNode {
4122 node_type: "FunctionRename".to_string(),
4123 identifier: format!("{} -> {} (surgical)", op.old_name, op.new_name),
4124 original_content: format!("Renamed {} to {} (surgical mode)", op.old_name, op.new_name),
4125 location: NodeLocation {
4126 line: 1,
4127 column: 0,
4128 end_line: 1,
4129 end_column: 0,
4130 },
4131 };
4132
4133 Ok(ModificationResult {
4134 changed: true,
4135 modified_nodes: vec![backup_node],
4136 })
4137 }
4138 EditMode::Reformat => {
4139 let mut renamer = FunctionRenamer {
4141 old_name: op.old_name.clone(),
4142 new_name: op.new_name.clone(),
4143 path_resolver,
4144 modified: false,
4145 };
4146
4147 renamer.visit_file_mut(&mut self.syntax_tree);
4149
4150 if !renamer.modified {
4151 return Ok(ModificationResult {
4152 changed: false,
4153 modified_nodes: vec![],
4154 });
4155 }
4156
4157 self.content = prettyplease::unparse(&self.syntax_tree);
4159
4160 self.line_offsets = Self::compute_line_offsets(&self.content);
4162
4163 let backup_node = BackupNode {
4165 node_type: "FunctionRename".to_string(),
4166 identifier: format!("{} -> {}", op.old_name, op.new_name),
4167 original_content: format!("Renamed {} to {}", op.old_name, op.new_name),
4168 location: NodeLocation {
4169 line: 1,
4170 column: 0,
4171 end_line: 1,
4172 end_column: 0,
4173 },
4174 };
4175
4176 Ok(ModificationResult {
4177 changed: true,
4178 modified_nodes: vec![backup_node],
4179 })
4180 }
4181 }
4182 }
4183
4184 fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
4186 if line == 0 || line > self.line_offsets.len() {
4187 anyhow::bail!("Line {} out of range", line);
4188 }
4189
4190 let line_start = self.line_offsets[line - 1];
4191 Ok(line_start + column)
4192 }
4193}
4194
4195struct MatchArmAdder {
4197 target_function: Option<String>,
4198 arm_to_add: Arm,
4199 modified: bool,
4200 current_function: Option<String>,
4201 modified_function: Option<String>,
4202}
4203
4204impl VisitMut for MatchArmAdder {
4205 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4206 let prev_fn = self.current_function.clone();
4207 self.current_function = Some(node.sig.ident.to_string());
4208
4209 syn::visit_mut::visit_item_fn_mut(self, node);
4211
4212 self.current_function = prev_fn;
4213 }
4214
4215 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4216 if let Some(ref target) = self.target_function {
4218 if self.current_function.as_ref() != Some(target) {
4219 syn::visit_mut::visit_expr_match_mut(self, node);
4221 return;
4222 }
4223 }
4224
4225 let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
4227 let already_exists = node.arms.iter().any(|arm| {
4228 arm.pat.to_token_stream().to_string() == pattern_str
4229 });
4230
4231 if !already_exists {
4232 node.arms.push(self.arm_to_add.clone());
4234 self.modified = true;
4235 self.modified_function = self.current_function.clone();
4236 }
4237
4238 syn::visit_mut::visit_expr_match_mut(self, node);
4240 }
4241}
4242
4243struct MatchArmUpdater {
4245 target_function: Option<String>,
4246 pattern_to_match: String,
4247 new_body: syn::Expr,
4248 modified: bool,
4249 current_function: Option<String>,
4250 modified_function: Option<String>,
4251}
4252
4253impl VisitMut for MatchArmUpdater {
4254 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4255 let prev_fn = self.current_function.clone();
4256 self.current_function = Some(node.sig.ident.to_string());
4257
4258 syn::visit_mut::visit_item_fn_mut(self, node);
4259
4260 self.current_function = prev_fn;
4261 }
4262
4263 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4264 if let Some(ref target) = self.target_function {
4266 if self.current_function.as_ref() != Some(target) {
4267 syn::visit_mut::visit_expr_match_mut(self, node);
4268 return;
4269 }
4270 }
4271
4272 for arm in &mut node.arms {
4274 let pattern_str = arm.pat.to_token_stream().to_string();
4275 let pattern_normalized = pattern_str.replace(" ", "");
4277 let target_normalized = self.pattern_to_match.replace(" ", "");
4278
4279 if pattern_normalized == target_normalized {
4280 arm.body = Box::new(self.new_body.clone());
4281 self.modified = true;
4282 self.modified_function = self.current_function.clone();
4283 break;
4284 }
4285 }
4286
4287 syn::visit_mut::visit_expr_match_mut(self, node);
4288 }
4289}
4290
4291struct MatchArmRemover {
4293 target_function: Option<String>,
4294 pattern_to_remove: String,
4295 modified: bool,
4296 current_function: Option<String>,
4297 modified_function: Option<String>,
4298}
4299
4300impl VisitMut for MatchArmRemover {
4301 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4302 let prev_fn = self.current_function.clone();
4303 self.current_function = Some(node.sig.ident.to_string());
4304
4305 syn::visit_mut::visit_item_fn_mut(self, node);
4306
4307 self.current_function = prev_fn;
4308 }
4309
4310 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4311 if let Some(ref target) = self.target_function {
4313 if self.current_function.as_ref() != Some(target) {
4314 syn::visit_mut::visit_expr_match_mut(self, node);
4315 return;
4316 }
4317 }
4318
4319 let mut index_to_remove = None;
4321 for (i, arm) in node.arms.iter().enumerate() {
4322 let pattern_str = arm.pat.to_token_stream().to_string();
4323 let pattern_normalized = pattern_str.replace(" ", "");
4325 let target_normalized = self.pattern_to_remove.replace(" ", "");
4326
4327 if pattern_normalized == target_normalized {
4328 index_to_remove = Some(i);
4329 break;
4330 }
4331 }
4332
4333 if let Some(index) = index_to_remove {
4334 node.arms.remove(index);
4335 self.modified = true;
4336 self.modified_function = self.current_function.clone();
4337 }
4338
4339 syn::visit_mut::visit_expr_match_mut(self, node);
4340 }
4341}
4342
4343struct MultiMatchArmAdder {
4345 target_function: Option<String>,
4346 arms_to_add: Vec<(String, Arm)>, modified: bool,
4348 current_function: Option<String>,
4349 modified_function: Option<String>,
4350}
4351
4352impl VisitMut for MultiMatchArmAdder {
4353 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4354 let prev_fn = self.current_function.clone();
4355 self.current_function = Some(node.sig.ident.to_string());
4356
4357 syn::visit_mut::visit_item_fn_mut(self, node);
4358
4359 self.current_function = prev_fn;
4360 }
4361
4362 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4363 if let Some(ref target) = self.target_function {
4365 if self.current_function.as_ref() != Some(target) {
4366 syn::visit_mut::visit_expr_match_mut(self, node);
4367 return;
4368 }
4369 }
4370
4371 for (pattern_str, arm) in &self.arms_to_add {
4373 let already_exists = node.arms.iter().any(|existing_arm| {
4375 existing_arm.pat.to_token_stream().to_string() == *pattern_str
4376 });
4377
4378 if !already_exists {
4379 node.arms.push(arm.clone());
4380 self.modified = true;
4381 self.modified_function = self.current_function.clone();
4382 }
4383 }
4384
4385 syn::visit_mut::visit_expr_match_mut(self, node);
4386 }
4387}
4388
4389struct StructLiteralFieldAdder {
4391 struct_name: String,
4392 field_def: String,
4393 field_name: String,
4394 position: InsertPosition,
4395 path_resolver: Option<PathResolver>,
4396 modified: bool,
4397}
4398
4399impl VisitMut for StructLiteralFieldAdder {
4400 fn visit_expr_mut(&mut self, node: &mut Expr) {
4401 if let Expr::Struct(expr_struct) = node {
4403 let is_match = if let Some(resolver) = &self.path_resolver {
4404 resolver.matches_target(&expr_struct.path)
4406 } else {
4407 if self.struct_name.contains("::") {
4413 if self.struct_name.starts_with("*::") {
4415 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
4418 .map(|seg| seg.ident.to_string() == target_name)
4419 .unwrap_or(false)
4420 } else {
4421 let path_str = expr_struct.path.segments.iter()
4423 .map(|seg| seg.ident.to_string())
4424 .collect::<Vec<_>>()
4425 .join("::");
4426 path_str == self.struct_name
4427 }
4428 } else {
4429 expr_struct.path.segments.len() == 1
4431 && expr_struct.path.segments.last()
4432 .map(|seg| seg.ident.to_string())
4433 .as_ref() == Some(&self.struct_name)
4434 }
4435 };
4436
4437 if is_match {
4438 let field_exists = expr_struct.fields.iter().any(|fv| {
4440 fv.member.to_token_stream().to_string() == self.field_name
4441 });
4442
4443 if !field_exists {
4444 let field_value_code = format!("{{ {} }}", self.field_def);
4447 if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
4448 if let Some(new_fv) = expr.fields.first() {
4449 match &self.position {
4451 InsertPosition::First => {
4452 expr_struct.fields.insert(0, new_fv.clone());
4453 self.modified = true;
4454 }
4455 InsertPosition::Last => {
4456 expr_struct.fields.push(new_fv.clone());
4457 self.modified = true;
4458 }
4459 InsertPosition::After(after_field) => {
4460 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4462 fv.member.to_token_stream().to_string() == *after_field
4463 }) {
4464 expr_struct.fields.insert(pos + 1, new_fv.clone());
4465 self.modified = true;
4466 }
4467 }
4468 InsertPosition::Before(before_field) => {
4469 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4471 fv.member.to_token_stream().to_string() == *before_field
4472 }) {
4473 expr_struct.fields.insert(pos, new_fv.clone());
4474 self.modified = true;
4475 }
4476 }
4477 }
4478 }
4479 }
4480 }
4481 }
4482 }
4483
4484 syn::visit_mut::visit_expr_mut(self, node);
4487 }
4488}
4489
4490struct EnumVariantRenamer {
4492 enum_name: String,
4493 old_variant: String,
4494 new_variant: String,
4495 path_resolver: Option<PathResolver>,
4496 modified: bool,
4497}
4498
4499impl EnumVariantRenamer {
4500 fn rename_path(&mut self, path: &mut syn::Path) {
4510 let segments: Vec<_> = path.segments.iter().collect();
4512 let len = segments.len();
4513
4514 if len >= 2 {
4515 let potential_variant = &segments[len - 1];
4517 let potential_enum = &segments[len - 2];
4518
4519 if potential_enum.ident == self.enum_name
4520 && potential_variant.ident == self.old_variant
4521 {
4522 if let Some(resolver) = &self.path_resolver {
4526 let enum_path = syn::Path {
4528 leading_colon: path.leading_colon,
4529 segments: path.segments.iter()
4530 .take(len - 1)
4531 .cloned()
4532 .collect(),
4533 };
4534
4535 if resolver.matches_target(&enum_path) {
4537 path.segments[len - 1].ident = syn::Ident::new(
4538 &self.new_variant,
4539 path.segments[len - 1].ident.span()
4540 );
4541 self.modified = true;
4542 }
4543 } else {
4544 if len == 2 {
4547 path.segments[1].ident = syn::Ident::new(
4548 &self.new_variant,
4549 path.segments[1].ident.span()
4550 );
4551 self.modified = true;
4552 }
4553 }
4554 }
4555 } else if len == 1 {
4556 if segments[0].ident == self.old_variant {
4558 if self.path_resolver.is_none() {
4562 path.segments[0].ident = syn::Ident::new(
4563 &self.new_variant,
4564 path.segments[0].ident.span()
4565 );
4566 self.modified = true;
4567 }
4568 }
4569 }
4570 }
4571}
4572
4573impl VisitMut for EnumVariantRenamer {
4574 fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
4576 if node.ident == self.enum_name {
4577 for variant in &mut node.variants {
4578 if variant.ident == self.old_variant {
4579 variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
4580 self.modified = true;
4581 }
4582 }
4583 }
4584
4585 syn::visit_mut::visit_item_enum_mut(self, node);
4587 }
4588
4589 fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
4591 match pat {
4592 syn::Pat::TupleStruct(tuple_struct) => {
4593 self.rename_path(&mut tuple_struct.path);
4594 }
4595 syn::Pat::Struct(struct_pat) => {
4596 self.rename_path(&mut struct_pat.path);
4597 }
4598 syn::Pat::Path(path_pat) => {
4599 self.rename_path(&mut path_pat.path);
4600 }
4601 _ => {}
4602 }
4603
4604 syn::visit_mut::visit_pat_mut(self, pat);
4606 }
4607
4608 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4610 match expr {
4611 syn::Expr::Path(expr_path) => {
4612 self.rename_path(&mut expr_path.path);
4613 }
4614 syn::Expr::Call(call) => {
4615 if let syn::Expr::Path(path) = &mut *call.func {
4616 self.rename_path(&mut path.path);
4617 }
4618 }
4619 syn::Expr::Struct(struct_expr) => {
4620 self.rename_path(&mut struct_expr.path);
4621 }
4622 _ => {}
4623 }
4624
4625 syn::visit_mut::visit_expr_mut(self, expr);
4627 }
4628}
4629
4630struct EnumVariantReplacementCollector {
4632 enum_name: String,
4633 old_variant: String,
4634 new_variant: String,
4635 path_resolver: Option<PathResolver>,
4636 replacements: Vec<crate::surgical::Replacement>,
4637}
4638
4639impl EnumVariantReplacementCollector {
4640 fn collect_path_replacement(&mut self, path: &syn::Path) {
4642 let segments: Vec<_> = path.segments.iter().collect();
4643 let len = segments.len();
4644
4645 if len >= 2 {
4646 let potential_variant = &segments[len - 1];
4647 let potential_enum = &segments[len - 2];
4648
4649 if potential_enum.ident == self.enum_name
4650 && potential_variant.ident == self.old_variant
4651 {
4652 let should_rename = if let Some(resolver) = &self.path_resolver {
4656 let enum_path = syn::Path {
4657 leading_colon: path.leading_colon,
4658 segments: path.segments.iter()
4659 .take(len - 1)
4660 .cloned()
4661 .collect(),
4662 };
4663 resolver.matches_target(&enum_path)
4664 } else {
4665 len == 2
4667 };
4668
4669 if should_rename {
4670 let span = potential_variant.ident.span();
4671 let start = span.start();
4672 let end = span.end();
4673
4674 self.replacements.push(crate::surgical::Replacement::new(
4675 start,
4676 end,
4677 self.new_variant.clone(),
4678 ));
4679 }
4680 }
4681 } else if len == 1 && self.path_resolver.is_none() {
4682 if segments[0].ident == self.old_variant {
4684 let span = segments[0].ident.span();
4685 let start = span.start();
4686 let end = span.end();
4687
4688 self.replacements.push(crate::surgical::Replacement::new(
4689 start,
4690 end,
4691 self.new_variant.clone(),
4692 ));
4693 }
4694 }
4695 }
4696}
4697
4698impl<'ast> syn::visit::Visit<'ast> for EnumVariantReplacementCollector {
4699 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
4700 if node.ident == self.enum_name {
4701 for variant in &node.variants {
4702 if variant.ident == self.old_variant {
4703 let span = variant.ident.span();
4704 let start = span.start();
4705 let end = span.end();
4706
4707 self.replacements.push(crate::surgical::Replacement::new(
4708 start,
4709 end,
4710 self.new_variant.clone(),
4711 ));
4712 }
4713 }
4714 }
4715 syn::visit::visit_item_enum(self, node);
4716 }
4717
4718 fn visit_pat(&mut self, pat: &'ast syn::Pat) {
4719 match pat {
4720 syn::Pat::TupleStruct(tuple_struct) => {
4721 self.collect_path_replacement(&tuple_struct.path);
4722 }
4723 syn::Pat::Struct(struct_pat) => {
4724 self.collect_path_replacement(&struct_pat.path);
4725 }
4726 syn::Pat::Path(path_pat) => {
4727 self.collect_path_replacement(&path_pat.path);
4728 }
4729 _ => {}
4730 }
4731 syn::visit::visit_pat(self, pat);
4732 }
4733
4734 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4735 match expr {
4736 syn::Expr::Path(expr_path) => {
4737 self.collect_path_replacement(&expr_path.path);
4738 }
4739 syn::Expr::Call(call) => {
4740 if let syn::Expr::Path(path) = &*call.func {
4741 self.collect_path_replacement(&path.path);
4742 }
4743 }
4744 syn::Expr::Struct(struct_expr) => {
4745 self.collect_path_replacement(&struct_expr.path);
4746 }
4747 _ => {}
4748 }
4749 syn::visit::visit_expr(self, expr);
4750 }
4751}
4752
4753struct FunctionRenamer {
4755 old_name: String,
4756 new_name: String,
4757 path_resolver: Option<PathResolver>,
4758 modified: bool,
4759}
4760
4761impl FunctionRenamer {
4762 fn rename_ident(&mut self, ident: &mut syn::Ident) {
4764 if ident == &self.old_name {
4765 *ident = syn::Ident::new(&self.new_name, ident.span());
4766 self.modified = true;
4767 }
4768 }
4769
4770 fn matches_target_function(&self, path: &syn::Path) -> bool {
4772 if let Some(resolver) = &self.path_resolver {
4773 resolver.matches_target(path)
4774 } else {
4775 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4777 }
4778 }
4779}
4780
4781impl VisitMut for FunctionRenamer {
4782 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4783 self.rename_ident(&mut node.sig.ident);
4785 syn::visit_mut::visit_item_fn_mut(self, node);
4786 }
4787
4788 fn visit_impl_item_fn_mut(&mut self, node: &mut syn::ImplItemFn) {
4789 self.rename_ident(&mut node.sig.ident);
4791 syn::visit_mut::visit_impl_item_fn_mut(self, node);
4792 }
4793
4794 fn visit_trait_item_fn_mut(&mut self, node: &mut syn::TraitItemFn) {
4795 self.rename_ident(&mut node.sig.ident);
4797 syn::visit_mut::visit_trait_item_fn_mut(self, node);
4798 }
4799
4800 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4801 match expr {
4802 syn::Expr::Call(call) => {
4803 if let syn::Expr::Path(expr_path) = &mut *call.func {
4805 if self.matches_target_function(&expr_path.path) {
4806 if let Some(last_seg) = expr_path.path.segments.last_mut() {
4807 self.rename_ident(&mut last_seg.ident);
4808 }
4809 }
4810 }
4811 }
4812 syn::Expr::Path(expr_path) => {
4813 if self.matches_target_function(&expr_path.path) {
4815 if let Some(last_seg) = expr_path.path.segments.last_mut() {
4816 self.rename_ident(&mut last_seg.ident);
4817 }
4818 }
4819 }
4820 _ => {}
4821 }
4822 syn::visit_mut::visit_expr_mut(self, expr);
4823 }
4824}
4825
4826struct FunctionReplacementCollector {
4828 old_name: String,
4829 new_name: String,
4830 path_resolver: Option<PathResolver>,
4831 replacements: Vec<crate::surgical::Replacement>,
4832}
4833
4834impl FunctionReplacementCollector {
4835 fn collect_replacement(&mut self, ident: &syn::Ident) {
4837 if ident == &self.old_name {
4838 let span = ident.span();
4839 let start = span.start();
4840 let end = span.end();
4841
4842 self.replacements.push(crate::surgical::Replacement::new(
4843 start,
4844 end,
4845 self.new_name.clone(),
4846 ));
4847 }
4848 }
4849
4850 fn matches_target_function(&self, path: &syn::Path) -> bool {
4852 if let Some(resolver) = &self.path_resolver {
4853 resolver.matches_target(path)
4854 } else {
4855 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4857 }
4858 }
4859}
4860
4861impl<'ast> syn::visit::Visit<'ast> for FunctionReplacementCollector {
4862 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
4863 self.collect_replacement(&node.sig.ident);
4865 syn::visit::visit_item_fn(self, node);
4866 }
4867
4868 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
4869 self.collect_replacement(&node.sig.ident);
4871 syn::visit::visit_impl_item_fn(self, node);
4872 }
4873
4874 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
4875 self.collect_replacement(&node.sig.ident);
4877 syn::visit::visit_trait_item_fn(self, node);
4878 }
4879
4880 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4881 match expr {
4882 syn::Expr::Call(call) => {
4883 if let syn::Expr::Path(expr_path) = &*call.func {
4885 if self.matches_target_function(&expr_path.path) {
4886 if let Some(last_seg) = expr_path.path.segments.last() {
4887 self.collect_replacement(&last_seg.ident);
4888 }
4889 }
4890 }
4891 for arg in &call.args {
4893 syn::visit::visit_expr(self, arg);
4894 }
4895 return;
4897 }
4898 syn::Expr::Path(expr_path) => {
4899 if self.matches_target_function(&expr_path.path) {
4901 if let Some(last_seg) = expr_path.path.segments.last() {
4902 self.collect_replacement(&last_seg.ident);
4903 }
4904 }
4905 }
4906 _ => {}
4907 }
4908 syn::visit::visit_expr(self, expr);
4909 }
4910}
4911
4912fn generate_doc_comment(text: &str, style: &DocCommentStyle) -> String {
4918 match style {
4919 DocCommentStyle::Line => {
4920 text.lines()
4922 .map(|line| {
4923 if line.trim().is_empty() {
4924 "///".to_string()
4925 } else {
4926 format!("/// {}", line)
4927 }
4928 })
4929 .collect::<Vec<_>>()
4930 .join("\n")
4931 }
4932 DocCommentStyle::Block => {
4933 if text.contains('\n') {
4935 let lines = text.lines()
4937 .map(|line| format!(" * {}", line))
4938 .collect::<Vec<_>>()
4939 .join("\n");
4940 format!("/**\n{}\n */", lines)
4941 } else {
4942 format!("/** {} */", text)
4944 }
4945 }
4946 }
4947}
4948
4949fn extract_preceding_comment(content: &str, start_line: usize) -> Option<String> {
4952 if start_line == 0 {
4953 return None;
4954 }
4955
4956 let lines: Vec<&str> = content.lines().collect();
4957 if start_line > lines.len() {
4958 return None;
4959 }
4960
4961 let line_idx = start_line.saturating_sub(1); let mut comment_start = line_idx;
4965 let mut found_any_comment = false;
4966
4967 while comment_start > 0 {
4968 let prev_line = lines[comment_start - 1].trim();
4969
4970 let is_comment = prev_line.starts_with("///")
4972 || prev_line.starts_with("//!")
4973 || prev_line.starts_with("//")
4974 || prev_line.starts_with("/**")
4975 || prev_line.starts_with("/*!")
4976 || prev_line.starts_with("/*")
4977 || (prev_line.starts_with("*") && !prev_line.starts_with("*/"))
4978 || prev_line == "*/";
4979
4980 if is_comment {
4981 comment_start -= 1;
4982 found_any_comment = true;
4983 } else if prev_line.is_empty() && found_any_comment {
4984 comment_start -= 1;
4987 } else {
4988 break;
4989 }
4990 }
4991
4992 if !found_any_comment {
4993 return None;
4994 }
4995
4996 let comment_lines: Vec<String> = lines[comment_start..line_idx]
4998 .iter()
4999 .map(|&line| line.to_string())
5000 .collect();
5001
5002 if comment_lines.is_empty() {
5003 None
5004 } else {
5005 Some(comment_lines.join("\n"))
5006 }
5007}
5008
5009struct TargetFinder {
5011 target_type: String,
5012 target_name: String,
5013 found_position: Option<(usize, String)>, }
5015
5016impl TargetFinder {
5017 fn new(target_type: String, target_name: String) -> Self {
5018 Self {
5019 target_type,
5020 target_name,
5021 found_position: None,
5022 }
5023 }
5024}
5025
5026impl<'ast> syn::visit::Visit<'ast> for TargetFinder {
5027 fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
5028 if self.target_type == "struct" && node.ident.to_string() == self.target_name {
5029 let line = node.struct_token.span.start().line;
5032 self.found_position = Some((line, String::new()));
5033 }
5034 syn::visit::visit_item_struct(self, node);
5035 }
5036
5037 fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
5038 if self.target_type == "enum" && node.ident.to_string() == self.target_name {
5039 let line = node.enum_token.span.start().line;
5041 self.found_position = Some((line, String::new()));
5042 }
5043 syn::visit::visit_item_enum(self, node);
5044 }
5045
5046 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
5047 if self.target_type == "function" && node.sig.ident.to_string() == self.target_name {
5048 let line = node.sig.fn_token.span.start().line;
5050 self.found_position = Some((line, String::new()));
5051 }
5052 syn::visit::visit_item_fn(self, node);
5053 }
5054}
5055
5056impl RustEditor {
5057 pub fn add_doc_comment_surgical(
5059 &mut self,
5060 target_type: &str,
5061 target_name: &str,
5062 doc_text: &str,
5063 style: &DocCommentStyle,
5064 ) -> Result<ModificationResult> {
5065 use syn::visit::Visit;
5066
5067 let mut finder = TargetFinder::new(
5069 target_type.to_string(),
5070 target_name.to_string(),
5071 );
5072 finder.visit_file(&self.syntax_tree);
5073
5074 if let Some((line_num, _indent)) = finder.found_position {
5075 let line_idx = line_num.saturating_sub(1);
5077
5078 let comment = generate_doc_comment(doc_text, style);
5080
5081 let lines: Vec<&str> = self.content.lines().collect();
5083 if line_idx >= lines.len() {
5084 anyhow::bail!("Target not found at line {}", line_num);
5085 }
5086
5087 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5089
5090 let indent = target_line
5092 .chars()
5093 .take_while(|c| c.is_whitespace())
5094 .collect::<String>();
5095
5096 let mut new_lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
5098
5099 let comment_lines: Vec<String> = comment
5101 .lines()
5102 .map(|line| format!("{}{}", indent, line))
5103 .collect();
5104
5105 for (i, comment_line) in comment_lines.iter().rev().enumerate() {
5107 new_lines.insert(line_idx, comment_line.clone());
5108 }
5109
5110 self.content = new_lines.join("\n");
5112
5113 self.syntax_tree = syn::parse_str(&self.content)
5115 .context("Failed to re-parse after adding comment")?;
5116
5117 Ok(ModificationResult {
5118 changed: true,
5119 modified_nodes: vec![BackupNode {
5120 node_type: target_type.to_string(),
5121 identifier: target_name.to_string(),
5122 original_content: target_line,
5123 location: NodeLocation {
5124 line: line_num,
5125 column: 1,
5126 end_line: line_num,
5127 end_column: target_line_len,
5128 },
5129 }],
5130 })
5131 } else {
5132 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5133 }
5134 }
5135
5136 pub fn update_doc_comment_surgical(
5138 &mut self,
5139 target_type: &str,
5140 target_name: &str,
5141 doc_text: &str,
5142 style: &DocCommentStyle,
5143 ) -> Result<ModificationResult> {
5144 use syn::visit::Visit;
5145
5146 let mut finder = TargetFinder::new(
5148 target_type.to_string(),
5149 target_name.to_string(),
5150 );
5151 finder.visit_file(&self.syntax_tree);
5152
5153 if let Some((line_num, _indent)) = finder.found_position {
5154 let line_idx = line_num.saturating_sub(1);
5156
5157 let lines: Vec<&str> = self.content.lines().collect();
5159 if line_idx >= lines.len() {
5160 anyhow::bail!("Target not found at line {}", line_num);
5161 }
5162
5163 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5165
5166 let indent = target_line
5168 .chars()
5169 .take_while(|c| c.is_whitespace())
5170 .collect::<String>();
5171
5172 let mut doc_comment_start = line_idx;
5174 while doc_comment_start > 0 {
5175 let prev_line = lines[doc_comment_start - 1].trim();
5176 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5177 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5178 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5179 prev_line == "*/" {
5180 doc_comment_start -= 1;
5181 } else {
5182 break;
5183 }
5184 }
5185
5186 let mut new_lines: Vec<String> = Vec::new();
5188
5189 for i in 0..doc_comment_start {
5191 new_lines.push(lines[i].to_string());
5192 }
5193
5194 let comment = generate_doc_comment(doc_text, style);
5196 let comment_lines: Vec<String> = comment
5197 .lines()
5198 .map(|line| format!("{}{}", indent, line))
5199 .collect();
5200
5201 for comment_line in comment_lines {
5202 new_lines.push(comment_line);
5203 }
5204
5205 for i in line_idx..lines.len() {
5207 new_lines.push(lines[i].to_string());
5208 }
5209
5210 self.content = new_lines.join("\n");
5212
5213 self.syntax_tree = syn::parse_str(&self.content)
5215 .context("Failed to re-parse after updating comment")?;
5216
5217 Ok(ModificationResult {
5218 changed: true,
5219 modified_nodes: vec![BackupNode {
5220 node_type: target_type.to_string(),
5221 identifier: target_name.to_string(),
5222 original_content: target_line,
5223 location: NodeLocation {
5224 line: line_num,
5225 column: 1,
5226 end_line: line_num,
5227 end_column: target_line_len,
5228 },
5229 }],
5230 })
5231 } else {
5232 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5233 }
5234 }
5235
5236 pub fn remove_doc_comment_surgical(
5238 &mut self,
5239 target_type: &str,
5240 target_name: &str,
5241 ) -> Result<ModificationResult> {
5242 use syn::visit::Visit;
5243
5244 let mut finder = TargetFinder::new(
5246 target_type.to_string(),
5247 target_name.to_string(),
5248 );
5249 finder.visit_file(&self.syntax_tree);
5250
5251 if let Some((line_num, _indent)) = finder.found_position {
5252 let line_idx = line_num.saturating_sub(1);
5254
5255 let lines: Vec<&str> = self.content.lines().collect();
5257 if line_idx >= lines.len() {
5258 anyhow::bail!("Target not found at line {}", line_num);
5259 }
5260
5261 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5263
5264 let mut doc_comment_start = line_idx;
5266 while doc_comment_start > 0 {
5267 let prev_line = lines[doc_comment_start - 1].trim();
5268 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5269 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5270 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5271 prev_line == "*/" {
5272 doc_comment_start -= 1;
5273 } else {
5274 break;
5275 }
5276 }
5277
5278 let mut new_lines: Vec<String> = Vec::new();
5280
5281 for i in 0..doc_comment_start {
5283 new_lines.push(lines[i].to_string());
5284 }
5285
5286 for i in line_idx..lines.len() {
5290 new_lines.push(lines[i].to_string());
5291 }
5292
5293 self.content = new_lines.join("\n");
5295
5296 self.syntax_tree = syn::parse_str(&self.content)
5298 .context("Failed to re-parse after removing comment")?;
5299
5300 Ok(ModificationResult {
5301 changed: true,
5302 modified_nodes: vec![BackupNode {
5303 node_type: target_type.to_string(),
5304 identifier: target_name.to_string(),
5305 original_content: target_line,
5306 location: NodeLocation {
5307 line: line_num,
5308 column: 1,
5309 end_line: line_num,
5310 end_column: target_line_len,
5311 },
5312 }],
5313 })
5314 } else {
5315 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5316 }
5317 }
5318}