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_struct(&mut self, node: &'ast syn::ExprStruct) {
741 let is_match = if let Some(resolver) = &self.path_resolver {
743 resolver.matches_target(&node.path)
744 } else {
745 if self.struct_name.contains("::") {
747 if self.struct_name.starts_with("*::") {
748 let target_name = &self.struct_name[3..];
749 node.path.segments.last()
750 .map(|seg| seg.ident.to_string() == target_name)
751 .unwrap_or(false)
752 } else {
753 let path_str = node.path.segments.iter()
754 .map(|seg| seg.ident.to_string())
755 .collect::<Vec<_>>()
756 .join("::");
757 path_str == self.struct_name
758 }
759 } else {
760 node.path.segments.len() == 1
761 && node.path.segments.last()
762 .map(|seg| seg.ident.to_string())
763 .as_ref() == Some(&self.struct_name)
764 }
765 };
766
767 if is_match {
768 let field_exists = node.fields.iter().any(|fv| {
770 fv.member.to_token_stream().to_string() == self.field_name
771 });
772
773 if !field_exists {
774 let insert_offset = if let Some(last_field) = node.fields.last() {
776 self.editor.span_to_byte_offset(last_field.span().end())
778 } else {
779 let brace_pos = self.editor.span_to_byte_offset(node.brace_token.span.join().start());
781 brace_pos + 1 };
783
784 let indent = if let Some(last_field) = node.fields.last() {
786 let line_start = self.editor.span_to_byte_offset(last_field.span().start());
787 self.editor.get_indentation(line_start).len()
788 } else {
789 let struct_start = self.editor.span_to_byte_offset(node.span().start());
791 self.editor.get_indentation(struct_start).len() + 4
792 };
793
794 self.insertion_points.push((insert_offset, indent));
795 }
796 }
797
798 syn::visit::visit_expr_struct(self, node);
799 }
800 }
801
802 let mut inserter = LiteralFieldInserter {
803 struct_name: op.struct_name.clone(),
804 field_name: field_name.clone(),
805 path_resolver: path_resolver.as_ref(),
806 insertion_points: Vec::new(),
807 editor: self,
808 };
809
810 inserter.visit_file(&self.syntax_tree);
811
812 if inserter.insertion_points.is_empty() {
813 return Ok(ModificationResult {
814 changed: false,
815 modified_nodes: vec![],
816 });
817 }
818
819 let mut points = inserter.insertion_points;
822 points.sort_by_key(|(offset, _)| std::cmp::Reverse(*offset));
823
824 for (insert_offset, indent_spaces) in points {
826 let indent = " ".repeat(indent_spaces);
827 let field_str = format!(",\n{}{}", indent, op.field_def);
828 self.content.insert_str(insert_offset, &field_str);
829 }
830
831 self.syntax_tree = syn::parse_str(&self.content)
833 .context("Failed to re-parse after adding struct literal fields")?;
834 self.line_offsets = Self::compute_line_offsets(&self.content);
835
836 Ok(ModificationResult {
837 changed: true,
838 modified_nodes: backup_nodes,
839 })
840 }
841
842 fn collect_struct_literal_backups(&self, struct_name: &str, path_resolver: Option<&PathResolver>) -> Vec<BackupNode> {
844 use syn::visit::Visit;
845 use syn::spanned::Spanned;
846
847 struct LiteralCollector<'a> {
848 struct_name: String,
849 path_resolver: Option<&'a PathResolver>,
850 backups: Vec<BackupNode>,
851 counter: usize,
852 editor: &'a RustEditor,
853 }
854
855 impl<'ast, 'a> Visit<'ast> for LiteralCollector<'a> {
856 fn visit_expr(&mut self, node: &'ast Expr) {
857 if let Expr::Struct(expr_struct) = node {
858 let matches = if let Some(resolver) = self.path_resolver {
859 resolver.matches_target(&expr_struct.path)
861 } else {
862 if self.struct_name.contains("::") {
868 if self.struct_name.starts_with("*::") {
870 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
873 .map(|seg| seg.ident.to_string() == target_name)
874 .unwrap_or(false)
875 } else {
876 let path_str = expr_struct.path.segments.iter()
878 .map(|seg| seg.ident.to_string())
879 .collect::<Vec<_>>()
880 .join("::");
881 path_str == self.struct_name
882 }
883 } else {
884 expr_struct.path.segments.len() == 1
886 && expr_struct.path.segments.last()
887 .map(|seg| seg.ident.to_string() == self.struct_name)
888 .unwrap_or(false)
889 }
890 };
891
892 if matches {
893 let start = self.editor.span_to_byte_offset(expr_struct.span().start());
895 let end = self.editor.span_to_byte_offset(expr_struct.span().end());
896 let original_source = &self.editor.content[start..end];
897
898 self.backups.push(BackupNode {
899 node_type: "struct-literal".to_string(),
900 identifier: format!("{}#{}", self.struct_name, self.counter),
901 original_content: original_source.to_string(),
902 location: NodeLocation {
903 line: 0, column: 0,
905 end_line: 0,
906 end_column: 0,
907 },
908 });
909 self.counter += 1;
910 }
911 }
912 syn::visit::visit_expr(self, node);
913 }
914 }
915
916 let mut collector = LiteralCollector {
917 struct_name: struct_name.to_string(),
918 path_resolver,
919 backups: Vec::new(),
920 counter: 0,
921 editor: self,
922 };
923
924 collector.visit_file(&self.syntax_tree);
925 collector.backups
926 }
927
928 pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
929 let item_enum = self.syntax_tree.items.iter()
931 .find_map(|item| {
932 if let Item::Enum(e) = item {
933 if e.ident == op.enum_name {
934 return Some(e.clone());
935 }
936 }
937 None
938 })
939 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
940
941 if let Some(ref where_filter) = op.where_filter {
943 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
944 return Ok(ModificationResult {
946 changed: false,
947 modified_nodes: vec![],
948 });
949 }
950 }
951
952 let backup_node = BackupNode {
954 node_type: "enum".to_string(),
955 identifier: op.enum_name.clone(),
956 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
957 location: self.span_to_location(item_enum.span()),
958 };
959
960 let modified = self.insert_enum_variant(&item_enum, op)?;
961
962 Ok(ModificationResult {
963 changed: modified,
964 modified_nodes: if modified { vec![backup_node] } else { vec![] },
965 })
966 }
967
968 fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
969 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
971 let dummy: ItemEnum = parse_str(&variant_code)
972 .context("Failed to parse variant definition")?;
973
974 let new_variant = dummy.variants.first()
975 .context("No variant found in definition")?
976 .clone();
977
978 let variant_name = new_variant.ident.to_string();
980 if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
981 return Ok(false);
983 }
984
985 let insert_pos = match &op.position {
987 InsertPosition::First => {
988 if let Some(first_var) = item_enum.variants.first() {
989 self.span_to_byte_offset(first_var.span().start())
990 } else {
991 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
992 brace_pos + 1
993 }
994 }
995 InsertPosition::Last => {
996 if let Some(last_var) = item_enum.variants.last() {
997 let end = self.span_to_byte_offset(last_var.span().end());
998 self.find_after_field_end(end)
999 } else {
1000 let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
1001 brace_pos + 1
1002 }
1003 }
1004 InsertPosition::After(name) => {
1005 let variant = item_enum.variants.iter()
1006 .find(|v| v.ident.to_string() == *name)
1007 .with_context(|| format!("Variant '{}' not found", name))?;
1008 let end = self.span_to_byte_offset(variant.span().end());
1009 self.find_after_field_end(end)
1010 }
1011 InsertPosition::Before(name) => {
1012 let variant = item_enum.variants.iter()
1013 .find(|v| v.ident.to_string() == *name)
1014 .with_context(|| format!("Variant '{}' not found", name))?;
1015 self.span_to_byte_offset(variant.span().start())
1016 }
1017 };
1018
1019 let indent = self.get_indentation(insert_pos);
1020 let variant_str = new_variant.to_token_stream().to_string();
1021 let insert_text = format!("\n{}{},", indent, variant_str);
1022
1023 self.content.insert_str(insert_pos, &insert_text);
1024 Ok(true)
1025 }
1026
1027 fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
1028 let item_enum = self.syntax_tree.items.iter()
1030 .find_map(|item| {
1031 if let Item::Enum(e) = item {
1032 if e.ident == op.enum_name {
1033 return Some(e.clone());
1034 }
1035 }
1036 None
1037 })
1038 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1039
1040 if let Some(ref where_filter) = op.where_filter {
1042 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1043 return Ok(ModificationResult {
1045 changed: false,
1046 modified_nodes: vec![],
1047 });
1048 }
1049 }
1050
1051 let backup_node = BackupNode {
1053 node_type: "enum".to_string(),
1054 identifier: op.enum_name.clone(),
1055 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1056 location: self.span_to_location(item_enum.span()),
1057 };
1058
1059 let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
1061 let dummy: ItemEnum = parse_str(&variant_code)
1062 .context("Failed to parse variant definition")?;
1063
1064 let new_variant = dummy.variants.first()
1065 .context("No variant found in definition")?
1066 .clone();
1067
1068 let variant_name = new_variant.ident.to_string();
1069
1070 let existing_variant = item_enum.variants.iter()
1072 .find(|v| v.ident.to_string() == variant_name)
1073 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
1074
1075 let start = self.span_to_byte_offset(existing_variant.span().start());
1077 let end = self.span_to_byte_offset(existing_variant.span().end());
1078
1079 let variant_str = new_variant.to_token_stream().to_string();
1081 self.content.replace_range(start..end, &variant_str);
1082
1083 Ok(ModificationResult {
1084 changed: true,
1085 modified_nodes: vec![backup_node],
1086 })
1087 }
1088
1089 pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
1090 let item_enum = self.syntax_tree.items.iter()
1092 .find_map(|item| {
1093 if let Item::Enum(e) = item {
1094 if e.ident == op.enum_name {
1095 return Some(e.clone());
1096 }
1097 }
1098 None
1099 })
1100 .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1101
1102 if let Some(ref where_filter) = op.where_filter {
1104 if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1105 return Ok(ModificationResult {
1107 changed: false,
1108 modified_nodes: vec![],
1109 });
1110 }
1111 }
1112
1113 let backup_node = BackupNode {
1115 node_type: "enum".to_string(),
1116 identifier: op.enum_name.clone(),
1117 original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1118 location: self.span_to_location(item_enum.span()),
1119 };
1120
1121 let variant_to_remove = item_enum.variants.iter()
1123 .find(|v| v.ident.to_string() == op.variant_name)
1124 .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
1125
1126 let start = self.span_to_byte_offset(variant_to_remove.span().start());
1128 let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
1129
1130 while end < self.content.len() {
1132 match self.content.as_bytes()[end] as char {
1133 ',' => {
1134 end += 1;
1135 if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
1136 end += 1;
1137 }
1138 break;
1139 }
1140 ' ' | '\t' => end += 1,
1141 '\n' => {
1142 end += 1;
1143 break;
1144 }
1145 _ => break,
1146 }
1147 }
1148
1149 let mut line_start = start;
1151 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1152 line_start -= 1;
1153 }
1154
1155 let before_variant = &self.content[line_start..start];
1156 if before_variant.trim().is_empty() {
1157 self.content.replace_range(line_start..end, "");
1158 } else {
1159 self.content.replace_range(start..end, "");
1160 }
1161
1162 Ok(ModificationResult {
1163 changed: true,
1164 modified_nodes: vec![backup_node],
1165 })
1166 }
1167
1168 pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1169 if op.auto_detect {
1170 self.add_missing_match_arms(op)
1172 } else {
1173 self.add_single_match_arm(op)
1175 }
1176 }
1177
1178 fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1179 let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
1181 let expr: syn::Expr = parse_str(&dummy_match)
1182 .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
1183
1184 let arm = if let syn::Expr::Match(match_expr) = expr {
1186 match_expr.arms.into_iter().next()
1187 .context("Failed to extract arm from dummy match")?
1188 } else {
1189 anyhow::bail!("Expected match expression");
1190 };
1191
1192 let backup_node = if let Some(ref fn_name) = op.function_name {
1194 self.get_function_backup(fn_name)?
1195 } else {
1196 BackupNode {
1199 node_type: "Unknown".to_string(),
1200 identifier: "match_expression".to_string(),
1201 original_content: String::new(),
1202 location: NodeLocation {
1203 line: 0,
1204 column: 0,
1205 end_line: 0,
1206 end_column: 0,
1207 },
1208 }
1209 };
1210
1211 let mut visitor = MatchArmAdder {
1213 target_function: op.function_name.clone(),
1214 arm_to_add: arm,
1215 modified: false,
1216 current_function: None,
1217 modified_function: None,
1218 };
1219
1220 visitor.visit_file_mut(&mut self.syntax_tree);
1221
1222 if visitor.modified {
1223 self.replace_modified_functions(&visitor.modified_function)?;
1225 Ok(ModificationResult {
1226 changed: true,
1227 modified_nodes: vec![backup_node],
1228 })
1229 } else {
1230 Ok(ModificationResult {
1231 changed: false,
1232 modified_nodes: vec![],
1233 })
1234 }
1235 }
1236
1237 fn unparse_item(&self, item: &Item) -> String {
1239 let temp_file = syn::File {
1240 shebang: None,
1241 attrs: Vec::new(),
1242 items: vec![item.clone()],
1243 };
1244 prettyplease::unparse(&temp_file).trim().to_string()
1245 }
1246
1247 fn reformat_item_isolated<F>(&mut self, predicate: F) -> Result<bool>
1250 where
1251 F: Fn(&Item) -> bool,
1252 {
1253 let original_syntax_tree: syn::File = syn::parse_str(&self.content)
1255 .context("Failed to parse original content")?;
1256
1257 let (item_index, original_item) = original_syntax_tree.items.iter()
1258 .enumerate()
1259 .find(|(_, item)| predicate(item))
1260 .ok_or_else(|| anyhow::anyhow!("Item not found"))?;
1261
1262 let start = self.span_to_byte_offset(original_item.span().start());
1264 let end = self.span_to_byte_offset(original_item.span().end());
1265
1266 if item_index >= self.syntax_tree.items.len() {
1268 anyhow::bail!("Item index out of bounds after modification");
1269 }
1270 let modified_item = &self.syntax_tree.items[item_index];
1271
1272 let formatted_item = self.unparse_item(modified_item);
1274
1275 self.content.replace_range(start..end, &formatted_item);
1277
1278 self.syntax_tree = syn::parse_str(&self.content)
1280 .context("Failed to re-parse after isolated prettyplease")?;
1281 self.line_offsets = Self::compute_line_offsets(&self.content);
1282
1283 Ok(true)
1284 }
1285
1286 fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
1288 for item in &self.syntax_tree.items {
1289 if let Item::Fn(f) = item {
1290 if f.sig.ident == fn_name {
1291 return Ok(BackupNode {
1292 node_type: "function".to_string(),
1293 identifier: fn_name.to_string(),
1294 original_content: self.unparse_item(&Item::Fn(f.clone())),
1295 location: self.span_to_location(f.span()),
1296 });
1297 }
1298 }
1299 }
1300 anyhow::bail!("Function '{}' not found", fn_name)
1301 }
1302
1303 fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1304 let enum_name = op.enum_name.as_ref()
1306 .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
1307
1308 let enum_variants = self.find_enum_variants(enum_name)?;
1310
1311 if enum_variants.is_empty() {
1312 anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
1313 }
1314
1315 let existing_patterns = self.find_existing_match_patterns(&op.function_name);
1317
1318 let mut missing_variants = Vec::new();
1320 for variant in &enum_variants {
1321 let pattern = format!("{}::{}", enum_name, variant);
1322 let pattern_normalized = pattern.replace(" ", "");
1323
1324 let exists = existing_patterns.iter().any(|p| {
1325 p.replace(" ", "") == pattern_normalized
1326 });
1327
1328 if !exists {
1329 missing_variants.push(variant.clone());
1330 }
1331 }
1332
1333 if missing_variants.is_empty() {
1334 println!("All enum variants already covered in match expressions");
1335 return Ok(ModificationResult {
1336 changed: false,
1337 modified_nodes: vec![],
1338 });
1339 }
1340
1341 let backup_node = if let Some(ref fn_name) = op.function_name {
1343 self.get_function_backup(fn_name)?
1344 } else {
1345 BackupNode {
1346 node_type: "Unknown".to_string(),
1347 identifier: "match_expression".to_string(),
1348 original_content: String::new(),
1349 location: NodeLocation {
1350 line: 0,
1351 column: 0,
1352 end_line: 0,
1353 end_column: 0,
1354 },
1355 }
1356 };
1357
1358 let mut arms_to_add = Vec::new();
1360 for variant in &missing_variants {
1361 let pattern = format!("{}::{}", enum_name, variant);
1362 let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
1363 let expr: syn::Expr = parse_str(&dummy_match)
1364 .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
1365
1366 if let syn::Expr::Match(match_expr) = expr {
1367 if let Some(arm) = match_expr.arms.into_iter().next() {
1368 arms_to_add.push((pattern.clone(), arm));
1369 }
1370 }
1371 }
1372
1373 let mut visitor = MultiMatchArmAdder {
1375 target_function: op.function_name.clone(),
1376 arms_to_add,
1377 modified: false,
1378 current_function: None,
1379 modified_function: None,
1380 };
1381
1382 visitor.visit_file_mut(&mut self.syntax_tree);
1383
1384 if visitor.modified {
1385 for variant in &missing_variants {
1387 println!("Added match arm for: {}::{}", enum_name, variant);
1388 }
1389
1390 self.replace_modified_functions(&visitor.modified_function)?;
1392 Ok(ModificationResult {
1393 changed: true,
1394 modified_nodes: vec![backup_node],
1395 })
1396 } else {
1397 Ok(ModificationResult {
1398 changed: false,
1399 modified_nodes: vec![],
1400 })
1401 }
1402 }
1403
1404 fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
1405 for item in &self.syntax_tree.items {
1407 if let Item::Enum(e) = item {
1408 if e.ident == enum_name {
1409 let variants: Vec<String> = e.variants.iter()
1410 .map(|v| v.ident.to_string())
1411 .collect();
1412 return Ok(variants);
1413 }
1414 }
1415 }
1416
1417 Ok(Vec::new())
1418 }
1419
1420 fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1421 use syn::visit::Visit;
1422
1423 struct PatternCollector {
1424 target_function: Option<String>,
1425 current_function: Option<String>,
1426 patterns: Vec<String>,
1427 }
1428
1429 impl<'ast> Visit<'ast> for PatternCollector {
1430 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1431 let prev_fn = self.current_function.clone();
1432 self.current_function = Some(node.sig.ident.to_string());
1433 syn::visit::visit_item_fn(self, node);
1434 self.current_function = prev_fn;
1435 }
1436
1437 fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1438 if let Some(ref target) = self.target_function {
1440 if self.current_function.as_ref() != Some(target) {
1441 syn::visit::visit_expr_match(self, node);
1442 return;
1443 }
1444 }
1445
1446 for arm in &node.arms {
1448 self.patterns.push(arm.pat.to_token_stream().to_string());
1449 }
1450
1451 syn::visit::visit_expr_match(self, node);
1452 }
1453 }
1454
1455 let mut collector = PatternCollector {
1456 target_function: function_name.clone(),
1457 current_function: None,
1458 patterns: Vec::new(),
1459 };
1460
1461 collector.visit_file(&self.syntax_tree);
1462 collector.patterns
1463 }
1464
1465 pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1466 let backup_node = if let Some(ref fn_name) = op.function_name {
1468 self.get_function_backup(fn_name)?
1469 } else {
1470 BackupNode {
1471 node_type: "Unknown".to_string(),
1472 identifier: "match_expression".to_string(),
1473 original_content: String::new(),
1474 location: NodeLocation {
1475 line: 0,
1476 column: 0,
1477 end_line: 0,
1478 end_column: 0,
1479 },
1480 }
1481 };
1482
1483 let new_body: syn::Expr = parse_str(&op.new_body)
1485 .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1486
1487 let mut visitor = MatchArmUpdater {
1489 target_function: op.function_name.clone(),
1490 pattern_to_match: op.pattern.clone(),
1491 new_body,
1492 modified: false,
1493 current_function: None,
1494 modified_function: None,
1495 };
1496
1497 visitor.visit_file_mut(&mut self.syntax_tree);
1498
1499 if visitor.modified {
1500 self.replace_modified_functions(&visitor.modified_function)?;
1502 Ok(ModificationResult {
1503 changed: true,
1504 modified_nodes: vec![backup_node],
1505 })
1506 } else {
1507 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1508 }
1509 }
1510
1511 pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1512 let backup_node = if let Some(ref fn_name) = op.function_name {
1514 self.get_function_backup(fn_name)?
1515 } else {
1516 BackupNode {
1517 node_type: "Unknown".to_string(),
1518 identifier: "match_expression".to_string(),
1519 original_content: String::new(),
1520 location: NodeLocation {
1521 line: 0,
1522 column: 0,
1523 end_line: 0,
1524 end_column: 0,
1525 },
1526 }
1527 };
1528
1529 let mut visitor = MatchArmRemover {
1531 target_function: op.function_name.clone(),
1532 pattern_to_remove: op.pattern.clone(),
1533 modified: false,
1534 current_function: None,
1535 modified_function: None,
1536 };
1537
1538 visitor.visit_file_mut(&mut self.syntax_tree);
1539
1540 if visitor.modified {
1541 self.replace_modified_functions(&visitor.modified_function)?;
1543 Ok(ModificationResult {
1544 changed: true,
1545 modified_nodes: vec![backup_node],
1546 })
1547 } else {
1548 anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1549 }
1550 }
1551
1552 pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1553 let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1555 let dummy: syn::ItemImpl = parse_str(&method_code)
1556 .context("Failed to parse method definition")?;
1557
1558 let new_method = dummy.items.first()
1559 .context("No method found in definition")?
1560 .clone();
1561
1562 let method_name = match &new_method {
1564 syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1565 _ => anyhow::bail!("Only method definitions are supported"),
1566 };
1567
1568 let impl_index = self.syntax_tree.items.iter().position(|item| {
1570 if let Item::Impl(impl_block) = item {
1571 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1573 if let Some(segment) = type_path.path.segments.last() {
1574 return segment.ident == op.target;
1575 }
1576 }
1577 }
1578 false
1579 }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1580
1581 let impl_block = match &self.syntax_tree.items[impl_index] {
1583 Item::Impl(i) => i,
1584 _ => unreachable!(),
1585 };
1586
1587 let method_exists = impl_block.items.iter().any(|item| {
1588 if let syn::ImplItem::Fn(f) = item {
1589 f.sig.ident == method_name
1590 } else {
1591 false
1592 }
1593 });
1594
1595 if method_exists {
1596 return Ok(ModificationResult {
1597 changed: false,
1598 modified_nodes: vec![],
1599 });
1600 }
1601
1602 let backup_node = BackupNode {
1604 node_type: "ItemImpl".to_string(),
1605 identifier: op.target.clone(),
1606 original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1607 location: self.span_to_location(impl_block.span()),
1608 };
1609
1610 let impl_span = impl_block.span();
1612
1613 match &mut self.syntax_tree.items[impl_index] {
1615 Item::Impl(impl_block) => {
1616 match &op.position {
1618 InsertPosition::First => {
1619 impl_block.items.insert(0, new_method);
1620 }
1621 InsertPosition::Last => {
1622 impl_block.items.push(new_method);
1623 }
1624 InsertPosition::After(name) => {
1625 let pos = impl_block.items.iter().position(|item| {
1626 if let syn::ImplItem::Fn(f) = item {
1627 f.sig.ident == name
1628 } else {
1629 false
1630 }
1631 }).with_context(|| format!("Method '{}' not found", name))?;
1632 impl_block.items.insert(pos + 1, new_method);
1633 }
1634 InsertPosition::Before(name) => {
1635 let pos = impl_block.items.iter().position(|item| {
1636 if let syn::ImplItem::Fn(f) = item {
1637 f.sig.ident == name
1638 } else {
1639 false
1640 }
1641 }).with_context(|| format!("Method '{}' not found", name))?;
1642 impl_block.items.insert(pos, new_method);
1643 }
1644 }
1645 }
1646 _ => unreachable!(),
1647 }
1648
1649 self.replace_formatted_item(impl_index, impl_span)?;
1651
1652 Ok(ModificationResult {
1653 changed: true,
1654 modified_nodes: vec![backup_node],
1655 })
1656 }
1657
1658 pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1659 let use_code = format!("use {};", op.use_path);
1661 let use_item: syn::ItemUse = parse_str(&use_code)
1662 .context("Failed to parse use statement")?;
1663
1664 let use_exists = self.syntax_tree.items.iter().any(|item| {
1666 if let Item::Use(existing_use) = item {
1667 existing_use.tree.to_token_stream().to_string() ==
1669 use_item.tree.to_token_stream().to_string()
1670 } else {
1671 false
1672 }
1673 });
1674
1675 if use_exists {
1676 return Ok(ModificationResult {
1677 changed: false,
1678 modified_nodes: vec![],
1679 });
1680 }
1681
1682 let backup_node = BackupNode {
1684 node_type: "ItemUse".to_string(),
1685 identifier: op.use_path.clone(),
1686 original_content: format!("use {};", op.use_path),
1687 location: NodeLocation {
1688 line: 0,
1689 column: 0,
1690 end_line: 0,
1691 end_column: 0,
1692 },
1693 };
1694
1695 let insert_index = match &op.position {
1697 InsertPosition::First => 0,
1698 InsertPosition::Last => {
1699 self.syntax_tree.items.iter()
1701 .rposition(|item| matches!(item, Item::Use(_)))
1702 .map(|i| i + 1)
1703 .unwrap_or(0)
1704 }
1705 InsertPosition::After(path) => {
1706 let pos = self.syntax_tree.items.iter().position(|item| {
1708 if let Item::Use(u) = item {
1709 u.tree.to_token_stream().to_string().contains(path)
1710 } else {
1711 false
1712 }
1713 }).with_context(|| format!("Use statement for '{}' not found", path))?;
1714 pos + 1
1715 }
1716 InsertPosition::Before(path) => {
1717 self.syntax_tree.items.iter().position(|item| {
1719 if let Item::Use(u) = item {
1720 u.tree.to_token_stream().to_string().contains(path)
1721 } else {
1722 false
1723 }
1724 }).with_context(|| format!("Use statement for '{}' not found", path))?
1725 }
1726 };
1727
1728 self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1730
1731 let insert_line_pos = if insert_index == 0 {
1734 0
1736 } else {
1737 let prev_item = &self.syntax_tree.items[insert_index - 1];
1739 let span = prev_item.span();
1740 let end_pos = self.span_to_byte_offset(span.end());
1741
1742 let mut line_end = end_pos;
1744 while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1745 line_end += 1;
1746 }
1747 if line_end < self.content.len() {
1749 line_end + 1
1750 } else {
1751 self.content.push('\n');
1753 self.content.len()
1754 }
1755 };
1756
1757 let use_str = format!("use {};\n", op.use_path);
1759
1760 self.content.insert_str(insert_line_pos, &use_str);
1762
1763 Ok(ModificationResult {
1764 changed: true,
1765 modified_nodes: vec![backup_node],
1766 })
1767 }
1768
1769 pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1770 let item_index = self.syntax_tree.items.iter().position(|item| {
1772 match (&op.target_type as &str, item) {
1773 ("struct", Item::Struct(s)) => s.ident == op.target_name,
1774 ("enum", Item::Enum(e)) => e.ident == op.target_name,
1775 _ => false,
1776 }
1777 }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1778
1779 let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1781 Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1782 Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1783 _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1784 };
1785
1786 if let Some(ref where_filter) = op.where_filter {
1788 if !self.matches_where_filter(item_attrs, where_filter)? {
1789 return Ok(ModificationResult {
1791 changed: false,
1792 modified_nodes: vec![],
1793 });
1794 }
1795 }
1796
1797 let backup_node = BackupNode {
1799 node_type: if op.target_type == "struct" { "struct" } else { "enum" }.to_string(),
1800 identifier: op.target_name.clone(),
1801 original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1802 location: self.span_to_location(item_span),
1803 };
1804
1805 let new_derives: Vec<String> = op.derives.iter()
1807 .filter(|d| !existing_derives.contains(&d.to_string()))
1808 .cloned()
1809 .collect();
1810
1811 if new_derives.is_empty() {
1812 return Ok(ModificationResult {
1814 changed: false,
1815 modified_nodes: vec![],
1816 });
1817 }
1818
1819 let mut all_derives = existing_derives;
1821 all_derives.extend(new_derives);
1822
1823 let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1825
1826 match &mut self.syntax_tree.items[item_index] {
1828 Item::Struct(s) => {
1829 Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1830 }
1831 Item::Enum(e) => {
1832 Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1833 }
1834 _ => unreachable!(),
1835 }
1836
1837 self.replace_formatted_item(item_index, item_span)?;
1839
1840 Ok(ModificationResult {
1841 changed: true,
1842 modified_nodes: vec![backup_node],
1843 })
1844 }
1845
1846 fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1848 let item_start_pos = self.span_to_byte_offset(original_span.start());
1850 let item_end_pos = self.span_to_byte_offset(original_span.end());
1851
1852 let mut actual_start = item_start_pos;
1854
1855 let mut temp_pos = item_start_pos;
1857 while temp_pos > 0 {
1858 temp_pos = temp_pos.saturating_sub(1);
1860 let mut line_start = temp_pos;
1861 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1862 line_start -= 1;
1863 }
1864
1865 let line = if temp_pos < self.content.len() {
1866 &self.content[line_start..temp_pos + 1]
1867 } else {
1868 &self.content[line_start..]
1869 };
1870 let trimmed = line.trim();
1871
1872 if trimmed.starts_with("#[") {
1873 actual_start = line_start;
1874 temp_pos = line_start;
1875 } else if trimmed.is_empty() {
1876 temp_pos = line_start;
1877 } else {
1878 break;
1879 }
1880
1881 if line_start == 0 {
1882 break;
1883 }
1884 }
1885
1886 let item_clone = self.syntax_tree.items[item_index].clone();
1888 let temp_file = syn::File {
1889 shebang: None,
1890 attrs: Vec::new(),
1891 items: vec![item_clone],
1892 };
1893
1894 let formatted = prettyplease::unparse(&temp_file);
1896 let formatted = formatted.trim();
1897
1898 self.content.replace_range(actual_start..item_end_pos, formatted);
1900
1901 Ok(())
1902 }
1903
1904 fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1906 for attr in attrs {
1907 if attr.path().is_ident("derive") {
1908 if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1909 let tokens_str = meta_list.tokens.to_string();
1910 return tokens_str
1911 .split(',')
1912 .map(|s| s.trim().to_string())
1913 .collect();
1914 }
1915 }
1916 }
1917 Vec::new()
1918 }
1919
1920 fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1925 if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1927 let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1928 let existing_derives = Self::extract_derives(attrs);
1929
1930 for required_trait in required_traits {
1932 if existing_derives.iter().any(|d| d == required_trait) {
1933 return Ok(true);
1934 }
1935 }
1936 return Ok(false);
1937 }
1938
1939 Ok(true)
1941 }
1942
1943 fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1945 let derive_str = derives.join(", ");
1946
1947 let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1949 let parsed: syn::ItemStruct = parse_str(&dummy)
1950 .context("Failed to parse derive attribute")?;
1951
1952 let new_attr = parsed.attrs.into_iter()
1953 .find(|a| a.path().is_ident("derive"))
1954 .context("Failed to extract derive attribute")?;
1955
1956 if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1958 attrs[pos] = new_attr;
1959 } else {
1960 attrs.insert(0, new_attr);
1962 }
1963
1964 Ok(())
1965 }
1966
1967 fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1969 if modified_function.is_none() {
1971 self.content = prettyplease::unparse(&self.syntax_tree);
1974 return Ok(());
1975 }
1976
1977 let original_syntax_tree: File = syn::parse_str(&self.content)
1979 .context("Failed to re-parse original content")?;
1980
1981 let function_name = modified_function.as_ref().unwrap();
1982
1983 let original_fn = original_syntax_tree.items.iter()
1985 .find_map(|item| {
1986 if let Item::Fn(f) = item {
1987 if f.sig.ident == function_name {
1988 return Some(f.clone());
1989 }
1990 }
1991 None
1992 })
1993 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1994
1995 let start = self.span_to_byte_offset(original_fn.span().start());
1997 let end = self.span_to_byte_offset(original_fn.span().end());
1998
1999 let modified_fn = self.syntax_tree.items.iter()
2001 .find_map(|item| {
2002 if let Item::Fn(f) = item {
2003 if f.sig.ident == function_name {
2004 return Some(f.clone());
2005 }
2006 }
2007 None
2008 })
2009 .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
2010
2011 let dummy_file = syn::File {
2013 shebang: None,
2014 attrs: Vec::new(),
2015 items: vec![Item::Fn(modified_fn)],
2016 };
2017
2018 let formatted_fn = prettyplease::unparse(&dummy_file);
2019
2020 let formatted_fn = formatted_fn.trim();
2022
2023 self.content.replace_range(start..end, formatted_fn);
2025
2026 Ok(())
2027 }
2028
2029 pub fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
2030 let line_idx = pos.line.saturating_sub(1);
2031 if line_idx < self.line_offsets.len() {
2032 self.line_offsets[line_idx] + pos.column
2033 } else {
2034 self.content.len()
2035 }
2036 }
2037
2038 fn find_after_field_end(&self, pos: usize) -> usize {
2039 let mut i = pos;
2041 while i < self.content.len() {
2042 match self.content.as_bytes()[i] as char {
2043 ',' => return i + 1,
2044 '\n' => return i + 1,
2045 _ => i += 1,
2046 }
2047 }
2048 pos
2049 }
2050
2051 fn get_indentation(&self, pos: usize) -> String {
2052 let mut line_start = pos;
2054 while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
2055 line_start -= 1;
2056 }
2057
2058 let mut indent = String::new();
2060 let mut i = line_start;
2061 while i < self.content.len() {
2062 match self.content.as_bytes()[i] as char {
2063 ' ' | '\t' => {
2064 indent.push(self.content.as_bytes()[i] as char);
2065 i += 1;
2066 }
2067 _ => break,
2068 }
2069 }
2070
2071 if indent.is_empty() {
2073 " ".to_string()
2074 } else {
2075 indent
2076 }
2077 }
2078
2079 pub fn to_string(&self) -> String {
2080 self.content.clone()
2081 }
2082
2083 pub fn get_syntax_tree(&self) -> &syn::File {
2085 &self.syntax_tree
2086 }
2087
2088 pub fn replace_range(&mut self, start: usize, end: usize, new_content: &str) -> Result<()> {
2090 if start > end || end > self.content.len() {
2091 anyhow::bail!("Invalid range: {}..{} (content length: {})", start, end, self.content.len());
2092 }
2093
2094 self.content.replace_range(start..end, new_content);
2095
2096 self.syntax_tree = syn::parse_str(&self.content)
2098 .context("Failed to re-parse after replace_range")?;
2099 self.line_offsets = Self::compute_line_offsets(&self.content);
2100
2101 Ok(())
2102 }
2103
2104 pub fn find_field_locations(&self, field_name: &str) -> Result<Vec<crate::operations::FieldLocation>> {
2106 use syn::visit::Visit;
2107 use crate::operations::{FieldLocation, FieldContext};
2108
2109 let mut locations = Vec::new();
2110
2111 for item in &self.syntax_tree.items {
2113 if let Item::Struct(s) = item {
2114 if let Fields::Named(ref fields) = s.fields {
2115 for field in &fields.named {
2116 if let Some(ident) = &field.ident {
2117 if ident == field_name {
2118 let field_type = quote::quote!(#field).to_string()
2119 .split(':')
2120 .nth(1)
2121 .map(|s| s.trim().to_string())
2122 .unwrap_or_else(|| "unknown".to_string());
2123 locations.push(FieldLocation {
2124 file_path: String::new(),
2125 line: s.span().start().line,
2126 context: FieldContext::StructDefinition {
2127 struct_name: s.ident.to_string(),
2128 field_type,
2129 },
2130 });
2131 }
2132 }
2133 }
2134 }
2135 }
2136
2137 if let Item::Enum(e) = item {
2139 for variant in &e.variants {
2140 if let Fields::Named(ref fields) = variant.fields {
2141 for field in &fields.named {
2142 if let Some(ident) = &field.ident {
2143 if ident == field_name {
2144 let field_type = quote::quote!(#field).to_string()
2145 .split(':')
2146 .nth(1)
2147 .map(|s| s.trim().to_string())
2148 .unwrap_or_else(|| "unknown".to_string());
2149 locations.push(FieldLocation {
2150 file_path: String::new(),
2151 line: variant.span().start().line,
2152 context: FieldContext::EnumVariantDefinition {
2153 enum_name: e.ident.to_string(),
2154 variant_name: variant.ident.to_string(),
2155 field_type,
2156 },
2157 });
2158 }
2159 }
2160 }
2161 }
2162 }
2163 }
2164 }
2165
2166 struct LiteralVisitor<'a> {
2168 field_name: &'a str,
2169 locations: Vec<FieldLocation>,
2170 }
2171
2172 impl<'ast, 'a> Visit<'ast> for LiteralVisitor<'a> {
2173 fn visit_expr(&mut self, node: &'ast Expr) {
2174 if let Expr::Struct(expr_struct) = node {
2175 for field_value in &expr_struct.fields {
2177 if let syn::Member::Named(ident) = &field_value.member {
2178 if ident == self.field_name {
2179 let struct_name = expr_struct.path.segments.iter()
2180 .map(|seg| seg.ident.to_string())
2181 .collect::<Vec<_>>()
2182 .join("::");
2183
2184 self.locations.push(FieldLocation {
2185 file_path: String::new(),
2186 line: expr_struct.span().start().line,
2187 context: FieldContext::StructLiteral {
2188 struct_name,
2189 },
2190 });
2191 break;
2192 }
2193 }
2194 }
2195 }
2196 syn::visit::visit_expr(self, node);
2197 }
2198 }
2199
2200 let mut visitor = LiteralVisitor {
2201 field_name,
2202 locations: Vec::new(),
2203 };
2204
2205 visitor.visit_file(&self.syntax_tree);
2206 locations.extend(visitor.locations);
2207
2208 Ok(locations)
2209 }
2210
2211 pub fn inspect(&self, node_type: Option<&str>, name_filter: Option<&str>, variant_filter: Option<&str>, include_comments: bool) -> Result<Vec<crate::operations::InspectResult>> {
2213 use syn::visit::Visit;
2214 use crate::operations::InspectResult;
2215
2216 let mut results = Vec::new();
2217
2218 if node_type.is_none() {
2220 let all_types = vec![
2221 "struct", "enum", "function", "impl-method", "trait", "const", "static", "type-alias", "mod",
2222 "struct-literal", "match-arm", "enum-usage", "function-call", "method-call", "macro-call", "identifier", "type-ref",
2223 ];
2224 for nt in all_types {
2225 let mut type_results = self.inspect(Some(nt), name_filter, variant_filter, include_comments)?;
2226 results.append(&mut type_results);
2227 }
2228 return Ok(results);
2229 }
2230
2231 let node_type = node_type.unwrap(); match node_type {
2234 "struct-literal" => {
2235 struct StructLiteralVisitor<'a> {
2237 results: &'a mut Vec<InspectResult>,
2238 name_filter: Option<&'a str>,
2239 editor: &'a RustEditor,
2240 include_comments: bool,
2241 }
2242
2243 impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
2244 fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
2245 let filter = match self.name_filter {
2251 Some(f) => f,
2252 None => {
2253 let struct_name = node.path.segments.last()
2255 .map(|seg| seg.ident.to_string())
2256 .unwrap_or_default();
2257
2258 let snippet = self.editor.format_expr_struct(node);
2259 let location = self.editor.span_to_location(node.span());
2260
2261 let preceding_comment = if self.include_comments {
2263 extract_preceding_comment(&self.editor.content, location.line)
2264 } else {
2265 None
2266 };
2267
2268 let preceding_comment = if self.include_comments {
2270 extract_preceding_comment(&self.editor.content, location.line)
2271 } else {
2272 None
2273 };
2274
2275 self.results.push(InspectResult {
2276 file_path: String::new(),
2277 node_type: "struct-literal".to_string(),
2278 identifier: struct_name,
2279 location,
2280 snippet,
2281 preceding_comment,
2282 });
2283
2284 syn::visit::visit_expr_struct(self, node);
2285 return;
2286 }
2287 };
2288
2289 let matches = if filter.contains("::") {
2291 if filter.starts_with("*::") {
2293 let target_name = &filter[3..]; node.path.segments.last()
2296 .map(|seg| seg.ident.to_string() == target_name)
2297 .unwrap_or(false)
2298 } else {
2299 let path_str = node.path.segments.iter()
2301 .map(|seg| seg.ident.to_string())
2302 .collect::<Vec<_>>()
2303 .join("::");
2304 path_str == filter
2305 }
2306 } else {
2307 node.path.get_ident()
2309 .map(|ident| ident.to_string() == filter)
2310 .unwrap_or(false)
2311 };
2312
2313 if !matches {
2314 syn::visit::visit_expr_struct(self, node);
2315 return;
2316 }
2317
2318 let struct_name = node.path.segments.last()
2320 .map(|seg| seg.ident.to_string())
2321 .unwrap_or_default();
2322
2323 let snippet = self.editor.format_expr_struct(node);
2325 let location = self.editor.span_to_location(node.span());
2326
2327 let preceding_comment = if self.include_comments {
2329 extract_preceding_comment(&self.editor.content, location.line)
2330 } else {
2331 None
2332 };
2333
2334 let preceding_comment = if self.include_comments {
2336 extract_preceding_comment(&self.editor.content, location.line)
2337 } else {
2338 None
2339 };
2340
2341 self.results.push(InspectResult {
2342 file_path: String::new(), node_type: "struct-literal".to_string(),
2344 identifier: struct_name,
2345 location,
2346 snippet,
2347 preceding_comment,
2348 });
2349
2350 syn::visit::visit_expr_struct(self, node);
2352 }
2353 }
2354
2355 let mut visitor = StructLiteralVisitor {
2356 results: &mut results,
2357 name_filter,
2358 editor: self,
2359 include_comments,
2360 };
2361
2362 for item in &self.syntax_tree.items {
2364 syn::visit::visit_item(&mut visitor, item);
2365 }
2366 }
2367 "match-arm" => {
2368 struct MatchArmVisitor<'a> {
2370 results: &'a mut Vec<InspectResult>,
2371 pattern_filter: Option<&'a str>,
2372 editor: &'a RustEditor,
2373 include_comments: bool,
2374 }
2375
2376 impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
2377 fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
2378 for arm in &node.arms {
2380 let pat = &arm.pat;
2382 let pattern_str = quote::quote!(#pat).to_string();
2383
2384 if let Some(filter) = self.pattern_filter {
2386 let normalized_pattern = pattern_str.replace(" ", "");
2388 let normalized_filter = filter.replace(" ", "");
2389
2390 if !normalized_pattern.contains(&normalized_filter) {
2391 continue;
2392 }
2393 }
2394
2395 let snippet = self.editor.format_match_arm(arm);
2397 let location = self.editor.span_to_location(arm.span());
2398
2399 let preceding_comment = if self.include_comments {
2401 extract_preceding_comment(&self.editor.content, location.line)
2402 } else {
2403 None
2404 };
2405
2406 let preceding_comment = if self.include_comments {
2408 extract_preceding_comment(&self.editor.content, location.line)
2409 } else {
2410 None
2411 };
2412
2413 self.results.push(InspectResult {
2414 file_path: String::new(), node_type: "match-arm".to_string(),
2416 identifier: pattern_str.replace(" ", ""),
2417 location,
2418 snippet,
2419 preceding_comment,
2420 });
2421 }
2422
2423 syn::visit::visit_expr_match(self, node);
2425 }
2426 }
2427
2428 let mut visitor = MatchArmVisitor {
2429 results: &mut results,
2430 pattern_filter: name_filter,
2431 editor: self,
2432 include_comments,
2433 };
2434
2435 for item in &self.syntax_tree.items {
2437 syn::visit::visit_item(&mut visitor, item);
2438 }
2439 }
2440 "enum-usage" => {
2441 struct EnumUsageVisitor<'a> {
2443 results: &'a mut Vec<InspectResult>,
2444 path_filter: Option<&'a str>,
2445 editor: &'a RustEditor,
2446 include_comments: bool,
2447 }
2448
2449 impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
2450 fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
2451 let path = &node.path;
2453 let path_str = quote::quote!(#path).to_string();
2454
2455 if let Some(filter) = self.path_filter {
2457 let normalized_path = path_str.replace(" ", "");
2459 let normalized_filter = filter.replace(" ", "");
2460
2461 if !normalized_path.contains(&normalized_filter) {
2462 syn::visit::visit_expr_path(self, node);
2463 return;
2464 }
2465 }
2466
2467 let snippet = self.editor.format_expr_path(node);
2469 let location = self.editor.span_to_location(node.span());
2470
2471 let preceding_comment = if self.include_comments {
2473 extract_preceding_comment(&self.editor.content, location.line)
2474 } else {
2475 None
2476 };
2477
2478 let preceding_comment = if self.include_comments {
2480 extract_preceding_comment(&self.editor.content, location.line)
2481 } else {
2482 None
2483 };
2484
2485 self.results.push(InspectResult {
2486 file_path: String::new(), node_type: "enum-usage".to_string(),
2488 identifier: path_str.replace(" ", ""),
2489 location,
2490 snippet,
2491 preceding_comment,
2492 });
2493
2494 syn::visit::visit_expr_path(self, node);
2496 }
2497 }
2498
2499 let mut visitor = EnumUsageVisitor {
2500 results: &mut results,
2501 path_filter: name_filter,
2502 editor: self,
2503 include_comments,
2504 };
2505
2506 for item in &self.syntax_tree.items {
2508 syn::visit::visit_item(&mut visitor, item);
2509 }
2510 }
2511 "function-call" => {
2512 struct FunctionCallVisitor<'a> {
2514 results: &'a mut Vec<InspectResult>,
2515 name_filter: Option<&'a str>,
2516 editor: &'a RustEditor,
2517 include_comments: bool,
2518 }
2519
2520 impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
2521 fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
2522 let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
2524 expr_path.path.segments.last()
2526 .map(|seg| seg.ident.to_string())
2527 .unwrap_or_default()
2528 } else {
2529 quote::quote!(#node.func).to_string()
2531 };
2532
2533 if let Some(filter) = self.name_filter {
2535 if func_name != filter {
2536 syn::visit::visit_expr_call(self, node);
2537 return;
2538 }
2539 }
2540
2541 let snippet = self.editor.format_expr_call(node);
2543 let location = self.editor.span_to_location(node.span());
2544
2545 let preceding_comment = if self.include_comments {
2547 extract_preceding_comment(&self.editor.content, location.line)
2548 } else {
2549 None
2550 };
2551
2552 let preceding_comment = if self.include_comments {
2554 extract_preceding_comment(&self.editor.content, location.line)
2555 } else {
2556 None
2557 };
2558
2559 self.results.push(InspectResult {
2560 file_path: String::new(), node_type: "function-call".to_string(),
2562 identifier: func_name,
2563 location,
2564 snippet,
2565 preceding_comment,
2566 });
2567
2568 syn::visit::visit_expr_call(self, node);
2570 }
2571 }
2572
2573 let mut visitor = FunctionCallVisitor {
2574 results: &mut results,
2575 name_filter,
2576 editor: self,
2577 include_comments,
2578 };
2579
2580 for item in &self.syntax_tree.items {
2582 syn::visit::visit_item(&mut visitor, item);
2583 }
2584 }
2585 "trait-method" => {
2586 struct TraitMethodVisitor<'a> {
2588 results: &'a mut Vec<InspectResult>,
2589 name_filter: Option<&'a str>,
2590 editor: &'a RustEditor,
2591 include_comments: bool,
2592 current_trait_name: Option<String>,
2593 }
2594
2595 impl<'ast, 'a> Visit<'ast> for TraitMethodVisitor<'a> {
2596 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
2597 let prev_trait_name = self.current_trait_name.clone();
2599 self.current_trait_name = Some(node.ident.to_string());
2600
2601 syn::visit::visit_item_trait(self, node);
2602
2603 self.current_trait_name = prev_trait_name;
2604 }
2605
2606 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
2607 let method_name = node.sig.ident.to_string();
2608
2609 let identifier = if let Some(ref trait_name) = self.current_trait_name {
2611 format!("{}::{}", trait_name, method_name)
2612 } else {
2613 method_name.clone()
2614 };
2615
2616 if let Some(filter) = self.name_filter {
2618 if !identifier.contains(filter) && method_name != filter {
2620 syn::visit::visit_trait_item_fn(self, node);
2621 return;
2622 }
2623 }
2624
2625 let snippet = self.editor.format_trait_item_fn(node);
2627 let location = self.editor.span_to_location(node.span());
2628
2629 let preceding_comment = if self.include_comments {
2631 extract_preceding_comment(&self.editor.content, location.line)
2632 } else {
2633 None
2634 };
2635
2636 self.results.push(InspectResult {
2637 file_path: String::new(),
2638 node_type: "trait-method".to_string(),
2639 identifier,
2640 location,
2641 snippet,
2642 preceding_comment,
2643 });
2644
2645 syn::visit::visit_trait_item_fn(self, node);
2646 }
2647 }
2648
2649 let mut visitor = TraitMethodVisitor {
2650 results: &mut results,
2651 name_filter,
2652 editor: self,
2653 include_comments,
2654 current_trait_name: None,
2655 };
2656
2657 for item in &self.syntax_tree.items {
2658 syn::visit::visit_item(&mut visitor, item);
2659 }
2660 }
2661 "method-call" => {
2662 struct MethodCallVisitor<'a> {
2664 results: &'a mut Vec<InspectResult>,
2665 name_filter: Option<&'a str>,
2666 editor: &'a RustEditor,
2667 include_comments: bool,
2668 }
2669
2670 impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
2671 fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
2672 let method_name = node.method.to_string();
2674
2675 if let Some(filter) = self.name_filter {
2677 if method_name != filter {
2678 syn::visit::visit_expr_method_call(self, node);
2679 return;
2680 }
2681 }
2682
2683 let snippet = self.editor.format_expr_method_call(node);
2685 let location = self.editor.span_to_location(node.span());
2686
2687 let preceding_comment = if self.include_comments {
2689 extract_preceding_comment(&self.editor.content, location.line)
2690 } else {
2691 None
2692 };
2693
2694 self.results.push(InspectResult {
2695 file_path: String::new(), node_type: "method-call".to_string(),
2697 identifier: method_name,
2698 location,
2699 snippet,
2700 preceding_comment,
2701 });
2702
2703 syn::visit::visit_expr_method_call(self, node);
2705 }
2706 }
2707
2708 let mut visitor = MethodCallVisitor {
2709 results: &mut results,
2710 name_filter,
2711 editor: self,
2712 include_comments,
2713 };
2714
2715 for item in &self.syntax_tree.items {
2717 syn::visit::visit_item(&mut visitor, item);
2718 }
2719 }
2720 "identifier" => {
2721 struct IdentifierVisitor<'a> {
2723 results: &'a mut Vec<InspectResult>,
2724 name_filter: Option<&'a str>,
2725 editor: &'a RustEditor,
2726 include_comments: bool,
2727 }
2728
2729 impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2730 fn visit_ident(&mut self, node: &'ast syn::Ident) {
2731 let ident_name = node.to_string();
2733
2734 if let Some(filter) = self.name_filter {
2736 if ident_name != filter {
2737 syn::visit::visit_ident(self, node);
2738 return;
2739 }
2740 }
2741
2742 let snippet = self.editor.format_ident(node);
2744 let location = self.editor.span_to_location(node.span());
2745
2746 let preceding_comment = if self.include_comments {
2748 extract_preceding_comment(&self.editor.content, location.line)
2749 } else {
2750 None
2751 };
2752
2753 self.results.push(InspectResult {
2754 file_path: String::new(), node_type: "identifier".to_string(),
2756 identifier: ident_name,
2757 location,
2758 snippet,
2759 preceding_comment,
2760 });
2761
2762 syn::visit::visit_ident(self, node);
2764 }
2765 }
2766
2767 let mut visitor = IdentifierVisitor {
2768 results: &mut results,
2769 name_filter,
2770 editor: self,
2771 include_comments,
2772 };
2773
2774 for item in &self.syntax_tree.items {
2776 syn::visit::visit_item(&mut visitor, item);
2777 }
2778 }
2779 "type-ref" => {
2780 struct TypeRefVisitor<'a> {
2782 results: &'a mut Vec<InspectResult>,
2783 name_filter: Option<&'a str>,
2784 editor: &'a RustEditor,
2785 include_comments: bool,
2786 }
2787
2788 impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2789 fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2790 let type_name = node.path.segments.last()
2792 .map(|seg| seg.ident.to_string())
2793 .unwrap_or_default();
2794
2795 if let Some(filter) = self.name_filter {
2797 if type_name != filter {
2798 syn::visit::visit_type_path(self, node);
2799 return;
2800 }
2801 }
2802
2803 let snippet = self.editor.format_type_path(node);
2805 let location = self.editor.span_to_location(node.span());
2806
2807 let preceding_comment = if self.include_comments {
2809 extract_preceding_comment(&self.editor.content, location.line)
2810 } else {
2811 None
2812 };
2813
2814 let path = &node.path;
2816 let path_str = quote::quote!(#path).to_string();
2817
2818 self.results.push(InspectResult {
2819 file_path: String::new(), node_type: "type-ref".to_string(),
2821 identifier: path_str.replace(" ", ""),
2822 location,
2823 snippet,
2824 preceding_comment,
2825 });
2826
2827 syn::visit::visit_type_path(self, node);
2829 }
2830 }
2831
2832 let mut visitor = TypeRefVisitor {
2833 results: &mut results,
2834 name_filter,
2835 editor: self,
2836 include_comments,
2837 };
2838
2839 for item in &self.syntax_tree.items {
2841 syn::visit::visit_item(&mut visitor, item);
2842 }
2843 }
2844 "macro-call" => {
2845 struct MacroCallVisitor<'a> {
2847 results: &'a mut Vec<InspectResult>,
2848 name_filter: Option<&'a str>,
2849 editor: &'a RustEditor,
2850 include_comments: bool,
2851 }
2852
2853 impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2854 fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2855 let macro_name = node.mac.path.segments.last()
2857 .map(|seg| seg.ident.to_string())
2858 .unwrap_or_default();
2859
2860 if let Some(filter) = self.name_filter {
2862 if macro_name != filter {
2863 syn::visit::visit_expr_macro(self, node);
2864 return;
2865 }
2866 }
2867
2868 let snippet = self.editor.format_expr_macro(node);
2870 let location = self.editor.span_to_location(node.span());
2871
2872 let preceding_comment = if self.include_comments {
2874 extract_preceding_comment(&self.editor.content, location.line)
2875 } else {
2876 None
2877 };
2878
2879 self.results.push(InspectResult {
2880 file_path: String::new(), node_type: "macro-call".to_string(),
2882 identifier: macro_name,
2883 location,
2884 snippet,
2885 preceding_comment,
2886 });
2887
2888 syn::visit::visit_expr_macro(self, node);
2890 }
2891
2892 fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2893 if let syn::Stmt::Macro(macro_stmt) = node {
2895 let macro_name = macro_stmt.mac.path.segments.last()
2896 .map(|seg| seg.ident.to_string())
2897 .unwrap_or_default();
2898
2899 if let Some(filter) = self.name_filter {
2901 if macro_name != filter {
2902 syn::visit::visit_stmt(self, node);
2903 return;
2904 }
2905 }
2906
2907 let snippet = self.editor.format_stmt_macro(macro_stmt);
2909 let location = self.editor.span_to_location(macro_stmt.span());
2910
2911 let preceding_comment = if self.include_comments {
2913 extract_preceding_comment(&self.editor.content, location.line)
2914 } else {
2915 None
2916 };
2917
2918 self.results.push(InspectResult {
2919 file_path: String::new(), node_type: "macro-call".to_string(),
2921 identifier: macro_name,
2922 location,
2923 snippet,
2924 preceding_comment,
2925 });
2926 }
2927
2928 syn::visit::visit_stmt(self, node);
2930 }
2931 }
2932
2933 let mut visitor = MacroCallVisitor {
2934 results: &mut results,
2935 name_filter,
2936 editor: self,
2937 include_comments,
2938 };
2939
2940 for item in &self.syntax_tree.items {
2942 syn::visit::visit_item(&mut visitor, item);
2943 }
2944 }
2945 "struct" => {
2946 struct StructDefVisitor<'a> {
2948 results: &'a mut Vec<InspectResult>,
2949 name_filter: Option<&'a str>,
2950 editor: &'a RustEditor,
2951 include_comments: bool,
2952 }
2953
2954 impl<'ast, 'a> Visit<'ast> for StructDefVisitor<'a> {
2955 fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
2956 let struct_name = node.ident.to_string();
2957
2958 if let Some(filter) = self.name_filter {
2960 if struct_name != filter {
2961 syn::visit::visit_item_struct(self, node);
2962 return;
2963 }
2964 }
2965
2966 let snippet = self.editor.format_item_struct(node);
2968 let location = self.editor.span_to_location(node.span());
2969
2970 let preceding_comment = if self.include_comments {
2972 extract_preceding_comment(&self.editor.content, location.line)
2973 } else {
2974 None
2975 };
2976
2977 self.results.push(InspectResult {
2978 file_path: String::new(),
2979 node_type: "struct".to_string(),
2980 identifier: struct_name,
2981 location,
2982 snippet,
2983 preceding_comment,
2984 });
2985
2986 syn::visit::visit_item_struct(self, node);
2987 }
2988 }
2989
2990 let mut visitor = StructDefVisitor {
2991 results: &mut results,
2992 name_filter,
2993 editor: self,
2994 include_comments,
2995 };
2996
2997 for item in &self.syntax_tree.items {
2998 syn::visit::visit_item(&mut visitor, item);
2999 }
3000 }
3001 "enum" => {
3002 struct EnumDefVisitor<'a> {
3009 results: &'a mut Vec<InspectResult>,
3010 name_filter: Option<&'a str>,
3011 variant_filter: Option<&'a str>,
3012 editor: &'a RustEditor,
3013 include_comments: bool,
3014 }
3015
3016 impl<'ast, 'a> Visit<'ast> for EnumDefVisitor<'a> {
3017 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
3018 let enum_name = node.ident.to_string();
3019
3020 let (enum_name_filter, implicit_variant_filter) = if let Some(filter) = self.name_filter {
3022 if filter.contains("::") {
3023 let parts: Vec<&str> = filter.split("::").collect();
3025 if parts.len() == 2 {
3026 if parts[0] == "*" {
3027 (None, Some(parts[1]))
3029 } else {
3030 (Some(parts[0]), Some(parts[1]))
3032 }
3033 } else {
3034 (Some(filter), None)
3036 }
3037 } else {
3038 (Some(filter), None)
3040 }
3041 } else {
3042 (None, None)
3043 };
3044
3045 let effective_variant_filter = self.variant_filter.or(implicit_variant_filter);
3047
3048 if let Some(filter) = enum_name_filter {
3050 if enum_name != filter {
3051 syn::visit::visit_item_enum(self, node);
3052 return;
3053 }
3054 }
3055
3056 if let Some(variant_name) = effective_variant_filter {
3058 let has_variant = node.variants.iter().any(|v| v.ident.to_string() == variant_name);
3059 if !has_variant {
3060 syn::visit::visit_item_enum(self, node);
3061 return;
3062 }
3063 }
3064
3065 let snippet = if let Some(variant_name) = effective_variant_filter {
3067 self.editor.format_item_enum_variant_only(node, variant_name)
3069 } else {
3070 self.editor.format_item_enum(node)
3071 };
3072
3073 let location = self.editor.span_to_location(node.span());
3074
3075 let preceding_comment = if self.include_comments {
3077 extract_preceding_comment(&self.editor.content, location.line)
3078 } else {
3079 None
3080 };
3081
3082 self.results.push(InspectResult {
3083 file_path: String::new(),
3084 node_type: "enum".to_string(),
3085 identifier: enum_name,
3086 location,
3087 snippet,
3088 preceding_comment,
3089 });
3090
3091 syn::visit::visit_item_enum(self, node);
3092 }
3093 }
3094
3095 let mut visitor = EnumDefVisitor {
3096 results: &mut results,
3097 name_filter,
3098 variant_filter,
3099 editor: self,
3100 include_comments,
3101 };
3102
3103 for item in &self.syntax_tree.items {
3104 syn::visit::visit_item(&mut visitor, item);
3105 }
3106 }
3107 "function" => {
3108 struct FunctionDefVisitor<'a> {
3110 results: &'a mut Vec<InspectResult>,
3111 name_filter: Option<&'a str>,
3112 editor: &'a RustEditor,
3113 include_comments: bool,
3114 }
3115
3116 impl<'ast, 'a> Visit<'ast> for FunctionDefVisitor<'a> {
3117 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3118 let fn_name = node.sig.ident.to_string();
3119
3120 if let Some(filter) = self.name_filter {
3122 if fn_name != filter {
3123 syn::visit::visit_item_fn(self, node);
3124 return;
3125 }
3126 }
3127
3128 let snippet = self.editor.format_item_fn(node);
3130 let location = self.editor.span_to_location(node.span());
3131
3132 let preceding_comment = if self.include_comments {
3134 extract_preceding_comment(&self.editor.content, location.line)
3135 } else {
3136 None
3137 };
3138
3139 self.results.push(InspectResult {
3140 file_path: String::new(),
3141 node_type: "function".to_string(),
3142 identifier: fn_name,
3143 location,
3144 snippet,
3145 preceding_comment,
3146 });
3147
3148 syn::visit::visit_item_fn(self, node);
3149 }
3150 }
3151
3152 let mut visitor = FunctionDefVisitor {
3153 results: &mut results,
3154 name_filter,
3155 editor: self,
3156 include_comments,
3157 };
3158
3159 for item in &self.syntax_tree.items {
3160 syn::visit::visit_item(&mut visitor, item);
3161 }
3162 }
3163 "impl-method" => {
3164 struct ImplMethodVisitor<'a> {
3166 results: &'a mut Vec<InspectResult>,
3167 name_filter: Option<&'a str>,
3168 editor: &'a RustEditor,
3169 include_comments: bool,
3170 current_impl_type: Option<String>,
3171 }
3172
3173 impl<'ast, 'a> Visit<'ast> for ImplMethodVisitor<'a> {
3174 fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
3175 let impl_type = if let syn::Type::Path(type_path) = &*node.self_ty {
3177 type_path.path.segments.last()
3178 .map(|seg| seg.ident.to_string())
3179 } else {
3180 None
3181 };
3182
3183 let prev_impl_type = self.current_impl_type.clone();
3184 self.current_impl_type = impl_type;
3185
3186 syn::visit::visit_item_impl(self, node);
3187
3188 self.current_impl_type = prev_impl_type;
3189 }
3190
3191 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
3192 let method_name = node.sig.ident.to_string();
3193
3194 let identifier = if let Some(ref impl_type) = self.current_impl_type {
3196 format!("{}::{}", impl_type, method_name)
3197 } else {
3198 method_name.clone()
3199 };
3200
3201 if let Some(filter) = self.name_filter {
3203 if !identifier.contains(filter) && method_name != filter {
3205 syn::visit::visit_impl_item_fn(self, node);
3206 return;
3207 }
3208 }
3209
3210 let snippet = self.editor.format_impl_item_fn(node);
3212 let location = self.editor.span_to_location(node.span());
3213
3214 let preceding_comment = if self.include_comments {
3216 extract_preceding_comment(&self.editor.content, location.line)
3217 } else {
3218 None
3219 };
3220
3221 self.results.push(InspectResult {
3222 file_path: String::new(),
3223 node_type: "impl-method".to_string(),
3224 identifier,
3225 location,
3226 snippet,
3227 preceding_comment,
3228 });
3229
3230 syn::visit::visit_impl_item_fn(self, node);
3231 }
3232 }
3233
3234 let mut visitor = ImplMethodVisitor {
3235 results: &mut results,
3236 name_filter,
3237 editor: self,
3238 include_comments,
3239 current_impl_type: None,
3240 };
3241
3242 for item in &self.syntax_tree.items {
3243 syn::visit::visit_item(&mut visitor, item);
3244 }
3245 }
3246 "trait" => {
3247 struct TraitDefVisitor<'a> {
3249 results: &'a mut Vec<InspectResult>,
3250 name_filter: Option<&'a str>,
3251 editor: &'a RustEditor,
3252 include_comments: bool,
3253 }
3254
3255 impl<'ast, 'a> Visit<'ast> for TraitDefVisitor<'a> {
3256 fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
3257 let trait_name = node.ident.to_string();
3258
3259 if let Some(filter) = self.name_filter {
3261 if trait_name != filter {
3262 syn::visit::visit_item_trait(self, node);
3263 return;
3264 }
3265 }
3266
3267 let snippet = self.editor.format_item_trait(node);
3269 let location = self.editor.span_to_location(node.span());
3270
3271 let preceding_comment = if self.include_comments {
3273 extract_preceding_comment(&self.editor.content, location.line)
3274 } else {
3275 None
3276 };
3277
3278 self.results.push(InspectResult {
3279 file_path: String::new(),
3280 node_type: "trait".to_string(),
3281 identifier: trait_name,
3282 location,
3283 snippet,
3284 preceding_comment,
3285 });
3286
3287 syn::visit::visit_item_trait(self, node);
3288 }
3289 }
3290
3291 let mut visitor = TraitDefVisitor {
3292 results: &mut results,
3293 name_filter,
3294 editor: self,
3295 include_comments,
3296 };
3297
3298 for item in &self.syntax_tree.items {
3299 syn::visit::visit_item(&mut visitor, item);
3300 }
3301 }
3302 "const" => {
3303 struct ConstDefVisitor<'a> {
3305 results: &'a mut Vec<InspectResult>,
3306 name_filter: Option<&'a str>,
3307 editor: &'a RustEditor,
3308 include_comments: bool,
3309 }
3310
3311 impl<'ast, 'a> Visit<'ast> for ConstDefVisitor<'a> {
3312 fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
3313 let const_name = node.ident.to_string();
3314
3315 if let Some(filter) = self.name_filter {
3317 if const_name != filter {
3318 syn::visit::visit_item_const(self, node);
3319 return;
3320 }
3321 }
3322
3323 let snippet = self.editor.format_item_const(node);
3325 let location = self.editor.span_to_location(node.span());
3326
3327 let preceding_comment = if self.include_comments {
3329 extract_preceding_comment(&self.editor.content, location.line)
3330 } else {
3331 None
3332 };
3333
3334 self.results.push(InspectResult {
3335 file_path: String::new(),
3336 node_type: "const".to_string(),
3337 identifier: const_name,
3338 location,
3339 snippet,
3340 preceding_comment,
3341 });
3342
3343 syn::visit::visit_item_const(self, node);
3344 }
3345 }
3346
3347 let mut visitor = ConstDefVisitor {
3348 results: &mut results,
3349 name_filter,
3350 editor: self,
3351 include_comments,
3352 };
3353
3354 for item in &self.syntax_tree.items {
3355 syn::visit::visit_item(&mut visitor, item);
3356 }
3357 }
3358 "static" => {
3359 struct StaticDefVisitor<'a> {
3361 results: &'a mut Vec<InspectResult>,
3362 name_filter: Option<&'a str>,
3363 editor: &'a RustEditor,
3364 include_comments: bool,
3365 }
3366
3367 impl<'ast, 'a> Visit<'ast> for StaticDefVisitor<'a> {
3368 fn visit_item_static(&mut self, node: &'ast syn::ItemStatic) {
3369 let static_name = node.ident.to_string();
3370
3371 if let Some(filter) = self.name_filter {
3373 if static_name != filter {
3374 syn::visit::visit_item_static(self, node);
3375 return;
3376 }
3377 }
3378
3379 let snippet = self.editor.format_item_static(node);
3381 let location = self.editor.span_to_location(node.span());
3382
3383 let preceding_comment = if self.include_comments {
3385 extract_preceding_comment(&self.editor.content, location.line)
3386 } else {
3387 None
3388 };
3389
3390 self.results.push(InspectResult {
3391 file_path: String::new(),
3392 node_type: "static".to_string(),
3393 identifier: static_name,
3394 location,
3395 snippet,
3396 preceding_comment,
3397 });
3398
3399 syn::visit::visit_item_static(self, node);
3400 }
3401 }
3402
3403 let mut visitor = StaticDefVisitor {
3404 results: &mut results,
3405 name_filter,
3406 editor: self,
3407 include_comments,
3408 };
3409
3410 for item in &self.syntax_tree.items {
3411 syn::visit::visit_item(&mut visitor, item);
3412 }
3413 }
3414 "type-alias" => {
3415 struct TypeAliasVisitor<'a> {
3417 results: &'a mut Vec<InspectResult>,
3418 name_filter: Option<&'a str>,
3419 editor: &'a RustEditor,
3420 include_comments: bool,
3421 }
3422
3423 impl<'ast, 'a> Visit<'ast> for TypeAliasVisitor<'a> {
3424 fn visit_item_type(&mut self, node: &'ast syn::ItemType) {
3425 let type_name = node.ident.to_string();
3426
3427 if let Some(filter) = self.name_filter {
3429 if type_name != filter {
3430 syn::visit::visit_item_type(self, node);
3431 return;
3432 }
3433 }
3434
3435 let snippet = self.editor.format_item_type(node);
3437 let location = self.editor.span_to_location(node.span());
3438
3439 let preceding_comment = if self.include_comments {
3441 extract_preceding_comment(&self.editor.content, location.line)
3442 } else {
3443 None
3444 };
3445
3446 self.results.push(InspectResult {
3447 file_path: String::new(),
3448 node_type: "type-alias".to_string(),
3449 identifier: type_name,
3450 location,
3451 snippet,
3452 preceding_comment,
3453 });
3454
3455 syn::visit::visit_item_type(self, node);
3456 }
3457 }
3458
3459 let mut visitor = TypeAliasVisitor {
3460 results: &mut results,
3461 name_filter,
3462 editor: self,
3463 include_comments,
3464 };
3465
3466 for item in &self.syntax_tree.items {
3467 syn::visit::visit_item(&mut visitor, item);
3468 }
3469 }
3470 "mod" => {
3471 struct ModDefVisitor<'a> {
3473 results: &'a mut Vec<InspectResult>,
3474 name_filter: Option<&'a str>,
3475 editor: &'a RustEditor,
3476 include_comments: bool,
3477 }
3478
3479 impl<'ast, 'a> Visit<'ast> for ModDefVisitor<'a> {
3480 fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
3481 let mod_name = node.ident.to_string();
3482
3483 if let Some(filter) = self.name_filter {
3485 if mod_name != filter {
3486 syn::visit::visit_item_mod(self, node);
3487 return;
3488 }
3489 }
3490
3491 let snippet = self.editor.format_item_mod(node);
3493 let location = self.editor.span_to_location(node.span());
3494
3495 let preceding_comment = if self.include_comments {
3497 extract_preceding_comment(&self.editor.content, location.line)
3498 } else {
3499 None
3500 };
3501
3502 self.results.push(InspectResult {
3503 file_path: String::new(),
3504 node_type: "mod".to_string(),
3505 identifier: mod_name,
3506 location,
3507 snippet,
3508 preceding_comment,
3509 });
3510
3511 syn::visit::visit_item_mod(self, node);
3512 }
3513 }
3514
3515 let mut visitor = ModDefVisitor {
3516 results: &mut results,
3517 name_filter,
3518 editor: self,
3519 include_comments,
3520 };
3521
3522 for item in &self.syntax_tree.items {
3523 syn::visit::visit_item(&mut visitor, item);
3524 }
3525 }
3526 _ => anyhow::bail!("Unsupported node type: {}", node_type),
3527 }
3528
3529 Ok(results)
3530 }
3531
3532 fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
3534 let start = self.span_to_byte_offset(expr.span().start());
3536 let end = self.span_to_byte_offset(expr.span().end());
3537
3538 let original = &self.content[start..end];
3540
3541 original.split_whitespace().collect::<Vec<_>>().join(" ")
3543 }
3544
3545 fn format_match_arm(&self, arm: &syn::Arm) -> String {
3547 let start = self.span_to_byte_offset(arm.span().start());
3549 let end = self.span_to_byte_offset(arm.span().end());
3550
3551 let original = &self.content[start..end];
3553
3554 original.split_whitespace().collect::<Vec<_>>().join(" ")
3556 }
3557
3558 fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
3560 let start = self.span_to_byte_offset(expr.span().start());
3562 let end = self.span_to_byte_offset(expr.span().end());
3563
3564 let original = &self.content[start..end];
3566
3567 original.split_whitespace().collect::<Vec<_>>().join(" ")
3569 }
3570
3571 fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
3573 let start = self.span_to_byte_offset(expr.span().start());
3575 let end = self.span_to_byte_offset(expr.span().end());
3576
3577 let original = &self.content[start..end];
3579
3580 original.split_whitespace().collect::<Vec<_>>().join(" ")
3582 }
3583
3584 fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
3586 let start = self.span_to_byte_offset(expr.span().start());
3588 let end = self.span_to_byte_offset(expr.span().end());
3589
3590 let original = &self.content[start..end];
3592
3593 original.split_whitespace().collect::<Vec<_>>().join(" ")
3595 }
3596
3597 fn format_ident(&self, ident: &syn::Ident) -> String {
3599 ident.to_string()
3600 }
3601
3602 fn format_type_path(&self, ty: &syn::TypePath) -> String {
3604 let start = self.span_to_byte_offset(ty.span().start());
3606 let end = self.span_to_byte_offset(ty.span().end());
3607
3608 let original = &self.content[start..end];
3610
3611 original.split_whitespace().collect::<Vec<_>>().join(" ")
3613 }
3614
3615 fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
3617 let start = self.span_to_byte_offset(expr.span().start());
3619 let end = self.span_to_byte_offset(expr.span().end());
3620
3621 let original = &self.content[start..end];
3623
3624 original.split_whitespace().collect::<Vec<_>>().join(" ")
3626 }
3627
3628 fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
3630 let start = self.span_to_byte_offset(stmt.span().start());
3632 let end = self.span_to_byte_offset(stmt.span().end());
3633
3634 let original = &self.content[start..end];
3636
3637 original.split_whitespace().collect::<Vec<_>>().join(" ")
3639 }
3640
3641 fn format_item_struct(&self, item: &syn::ItemStruct) -> String {
3643 let start = self.span_to_byte_offset(item.span().start());
3644 let end = self.span_to_byte_offset(item.span().end());
3645 let original = &self.content[start..end];
3646 original.to_string()
3647 }
3648
3649 fn format_item_enum(&self, item: &syn::ItemEnum) -> String {
3651 let start = self.span_to_byte_offset(item.span().start());
3652 let end = self.span_to_byte_offset(item.span().end());
3653 let original = &self.content[start..end];
3654 original.to_string()
3655 }
3656
3657 fn format_item_enum_variant_only(&self, item: &syn::ItemEnum, variant_name: &str) -> String {
3659 let variant = item.variants.iter()
3661 .find(|v| v.ident.to_string() == variant_name);
3662
3663 if let Some(variant) = variant {
3664 let enum_start = self.span_to_byte_offset(item.span().start());
3666 let variants_start = if !item.variants.is_empty() {
3667 self.span_to_byte_offset(item.variants.first().unwrap().span().start())
3668 } else {
3669 self.span_to_byte_offset(item.span().end())
3670 };
3671
3672 let header = &self.content[enum_start..variants_start].trim_end();
3674
3675 let variant_start = self.span_to_byte_offset(variant.span().start());
3677 let variant_end = self.span_to_byte_offset(variant.span().end());
3678 let variant_source = &self.content[variant_start..variant_end];
3679
3680 format!("{}\n {},\n // ... {} other variant{}\n}}",
3682 header,
3683 variant_source,
3684 item.variants.len() - 1,
3685 if item.variants.len() - 1 == 1 { "" } else { "s" }
3686 )
3687 } else {
3688 self.format_item_enum(item)
3690 }
3691 }
3692
3693 fn format_item_fn(&self, item: &syn::ItemFn) -> String {
3695 let start = self.span_to_byte_offset(item.span().start());
3696 let end = self.span_to_byte_offset(item.span().end());
3697 let original = &self.content[start..end];
3698 original.to_string()
3699 }
3700
3701 fn format_impl_item_fn(&self, item: &syn::ImplItemFn) -> String {
3703 let start = self.span_to_byte_offset(item.span().start());
3704 let end = self.span_to_byte_offset(item.span().end());
3705 let original = &self.content[start..end];
3706 original.to_string()
3707 }
3708
3709 fn format_trait_item_fn(&self, item: &syn::TraitItemFn) -> String {
3711 let start = self.span_to_byte_offset(item.span().start());
3712 let end = self.span_to_byte_offset(item.span().end());
3713 let original = &self.content[start..end];
3714 original.to_string()
3715 }
3716
3717 fn format_item_trait(&self, item: &syn::ItemTrait) -> String {
3719 let start = self.span_to_byte_offset(item.span().start());
3720 let end = self.span_to_byte_offset(item.span().end());
3721 let original = &self.content[start..end];
3722 original.to_string()
3723 }
3724
3725 fn format_item_const(&self, item: &syn::ItemConst) -> String {
3727 let start = self.span_to_byte_offset(item.span().start());
3728 let end = self.span_to_byte_offset(item.span().end());
3729 let original = &self.content[start..end];
3730 original.to_string()
3731 }
3732
3733 fn format_item_static(&self, item: &syn::ItemStatic) -> String {
3735 let start = self.span_to_byte_offset(item.span().start());
3736 let end = self.span_to_byte_offset(item.span().end());
3737 let original = &self.content[start..end];
3738 original.to_string()
3739 }
3740
3741 fn format_item_type(&self, item: &syn::ItemType) -> String {
3743 let start = self.span_to_byte_offset(item.span().start());
3744 let end = self.span_to_byte_offset(item.span().end());
3745 let original = &self.content[start..end];
3746 original.to_string()
3747 }
3748
3749 fn format_item_mod(&self, item: &syn::ItemMod) -> String {
3751 let start = self.span_to_byte_offset(item.span().start());
3752 let end = self.span_to_byte_offset(item.span().end());
3753 let original = &self.content[start..end];
3754 original.to_string()
3755 }
3756
3757 #[allow(dead_code)]
3759 pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
3760 for (index, item) in self.syntax_tree.items.iter().enumerate() {
3761 match (node_type, item) {
3762 ("struct", Item::Struct(s)) if s.ident == name => {
3763 return Ok(index);
3764 }
3765 ("enum", Item::Enum(e)) if e.ident == name => {
3766 return Ok(index);
3767 }
3768 ("fn", Item::Fn(f)) if f.sig.ident == name => {
3769 return Ok(index);
3770 }
3771 ("impl", Item::Impl(impl_block)) => {
3772 if let syn::Type::Path(type_path) = &*impl_block.self_ty {
3774 if let Some(segment) = type_path.path.segments.last() {
3775 if segment.ident == name {
3776 return Ok(index);
3777 }
3778 }
3779 }
3780 }
3781 _ => {}
3782 }
3783 }
3784
3785 anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
3786 }
3787
3788 #[allow(dead_code)]
3790 pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
3791 if index >= self.syntax_tree.items.len() {
3792 anyhow::bail!("Index {} out of bounds", index);
3793 }
3794
3795 self.syntax_tree.items[index] = new_item;
3797
3798 self.content = prettyplease::unparse(&self.syntax_tree);
3800
3801 self.line_offsets = Self::compute_line_offsets(&self.content);
3803
3804 Ok(())
3805 }
3806
3807 fn span_to_location(&self, span: Span) -> NodeLocation {
3808 let start = span.start();
3809 let end = span.end();
3810
3811 NodeLocation {
3812 line: start.line,
3813 column: start.column,
3814 end_line: end.line,
3815 end_column: end.column,
3816 }
3817 }
3818
3819 pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
3821 use crate::operations::{InspectResult, TransformAction};
3822
3823 let matches = self.inspect(Some(&op.node_type), op.name_filter.as_deref(), None, false)?;
3825
3826 let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
3828 matches.into_iter()
3829 .filter(|m| m.snippet.contains(content_filter))
3830 .collect()
3831 } else {
3832 matches
3833 };
3834
3835 if filtered_matches.is_empty() {
3836 return Ok(ModificationResult {
3837 changed: false,
3838 modified_nodes: vec![],
3839 });
3840 }
3841
3842 let mut sorted_matches = filtered_matches;
3845 sorted_matches.sort_by(|a, b| {
3846 b.location.line.cmp(&a.location.line)
3847 .then(b.location.column.cmp(&a.location.column))
3848 });
3849
3850 let mut modified_nodes = Vec::new();
3851
3852 for match_result in &sorted_matches {
3853 let backup_node = BackupNode {
3855 node_type: match_result.node_type.clone(),
3856 identifier: match_result.identifier.clone(),
3857 original_content: match_result.snippet.clone(),
3858 location: match_result.location.clone(),
3859 };
3860
3861 let start_offset = self.line_column_to_byte_offset(
3863 match_result.location.line,
3864 match_result.location.column
3865 )?;
3866 let end_offset = self.line_column_to_byte_offset(
3867 match_result.location.end_line,
3868 match_result.location.end_column
3869 )?;
3870
3871 let original_text = &self.content[start_offset..end_offset];
3873
3874 let replacement = match &op.action {
3876 TransformAction::Comment => {
3877 format!("// {}", original_text.replace("\n", "\n// "))
3879 }
3880 TransformAction::Remove => {
3881 String::new()
3883 }
3884 TransformAction::Replace { with } => {
3885 with.clone()
3887 }
3888 };
3889
3890 self.content.replace_range(start_offset..end_offset, &replacement);
3892
3893 self.line_offsets = Self::compute_line_offsets(&self.content);
3895
3896 modified_nodes.push(backup_node);
3897 }
3898
3899 if !modified_nodes.is_empty() {
3901 }
3905
3906 Ok(ModificationResult {
3907 changed: !modified_nodes.is_empty(),
3908 modified_nodes,
3909 })
3910 }
3911
3912 pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
3914 use crate::operations::EditMode;
3915
3916 let path_resolver = if let Some(enum_path) = &op.enum_path {
3918 let mut resolver = PathResolver::new(enum_path)
3919 .ok_or_else(|| anyhow::anyhow!("Invalid enum path: {}", enum_path))?;
3920
3921 resolver.scan_file(&self.syntax_tree);
3923 Some(resolver)
3924 } else {
3925 None
3926 };
3927
3928 match op.edit_mode {
3929 EditMode::Surgical => {
3930 use syn::visit::Visit;
3932 use crate::surgical::Replacement;
3933
3934 let mut collector = EnumVariantReplacementCollector {
3935 enum_name: op.enum_name.clone(),
3936 old_variant: op.old_variant.clone(),
3937 new_variant: op.new_variant.clone(),
3938 path_resolver,
3939 replacements: Vec::new(),
3940 };
3941
3942 collector.visit_file(&self.syntax_tree);
3943
3944 if collector.replacements.is_empty() {
3945 return Ok(ModificationResult {
3946 changed: false,
3947 modified_nodes: vec![],
3948 });
3949 }
3950
3951 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
3953
3954 self.line_offsets = Self::compute_line_offsets(&self.content);
3956
3957 self.syntax_tree = syn::parse_str(&self.content)
3959 .context("Failed to re-parse after surgical edit")?;
3960
3961 let backup_node = BackupNode {
3962 node_type: "EnumVariantRename".to_string(),
3963 identifier: format!("{}::{} -> {} (surgical)", op.enum_name, op.old_variant, op.new_variant),
3964 original_content: format!("Renamed {} to {} in enum {} (surgical mode)", op.old_variant, op.new_variant, op.enum_name),
3965 location: NodeLocation {
3966 line: 1,
3967 column: 0,
3968 end_line: 1,
3969 end_column: 0,
3970 },
3971 };
3972
3973 Ok(ModificationResult {
3974 changed: true,
3975 modified_nodes: vec![backup_node],
3976 })
3977 }
3978 EditMode::Reformat => {
3979 let mut renamer = EnumVariantRenamer {
3981 enum_name: op.enum_name.clone(),
3982 old_variant: op.old_variant.clone(),
3983 new_variant: op.new_variant.clone(),
3984 path_resolver,
3985 modified: false,
3986 };
3987
3988 renamer.visit_file_mut(&mut self.syntax_tree);
3990
3991 if !renamer.modified {
3992 return Ok(ModificationResult {
3993 changed: false,
3994 modified_nodes: vec![],
3995 });
3996 }
3997
3998 self.content = prettyplease::unparse(&self.syntax_tree);
4000
4001 self.line_offsets = Self::compute_line_offsets(&self.content);
4003
4004 let backup_node = BackupNode {
4006 node_type: "EnumVariantRename".to_string(),
4007 identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
4008 original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
4009 location: NodeLocation {
4010 line: 1,
4011 column: 0,
4012 end_line: 1,
4013 end_column: 0,
4014 },
4015 };
4016
4017 Ok(ModificationResult {
4018 changed: true,
4019 modified_nodes: vec![backup_node],
4020 })
4021 }
4022 }
4023 }
4024
4025 pub(crate) fn rename_function(&mut self, op: &crate::operations::RenameFunctionOp) -> Result<ModificationResult> {
4027 use crate::operations::EditMode;
4028
4029 let path_resolver = if let Some(function_path) = &op.function_path {
4031 let mut resolver = PathResolver::new(function_path)
4032 .ok_or_else(|| anyhow::anyhow!("Invalid function path: {}", function_path))?;
4033
4034 resolver.scan_file(&self.syntax_tree);
4036 Some(resolver)
4037 } else {
4038 None
4039 };
4040
4041 match op.edit_mode {
4042 EditMode::Surgical => {
4043 use syn::visit::Visit;
4045
4046 let mut collector = FunctionReplacementCollector {
4047 old_name: op.old_name.clone(),
4048 new_name: op.new_name.clone(),
4049 path_resolver,
4050 replacements: Vec::new(),
4051 };
4052
4053 collector.visit_file(&self.syntax_tree);
4054
4055 if collector.replacements.is_empty() {
4056 return Ok(ModificationResult {
4057 changed: false,
4058 modified_nodes: vec![],
4059 });
4060 }
4061
4062 self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
4064
4065 self.line_offsets = Self::compute_line_offsets(&self.content);
4067
4068 self.syntax_tree = syn::parse_str(&self.content)
4070 .context("Failed to re-parse after surgical edit")?;
4071
4072 let backup_node = BackupNode {
4073 node_type: "FunctionRename".to_string(),
4074 identifier: format!("{} -> {} (surgical)", op.old_name, op.new_name),
4075 original_content: format!("Renamed {} to {} (surgical mode)", op.old_name, op.new_name),
4076 location: NodeLocation {
4077 line: 1,
4078 column: 0,
4079 end_line: 1,
4080 end_column: 0,
4081 },
4082 };
4083
4084 Ok(ModificationResult {
4085 changed: true,
4086 modified_nodes: vec![backup_node],
4087 })
4088 }
4089 EditMode::Reformat => {
4090 let mut renamer = FunctionRenamer {
4092 old_name: op.old_name.clone(),
4093 new_name: op.new_name.clone(),
4094 path_resolver,
4095 modified: false,
4096 };
4097
4098 renamer.visit_file_mut(&mut self.syntax_tree);
4100
4101 if !renamer.modified {
4102 return Ok(ModificationResult {
4103 changed: false,
4104 modified_nodes: vec![],
4105 });
4106 }
4107
4108 self.content = prettyplease::unparse(&self.syntax_tree);
4110
4111 self.line_offsets = Self::compute_line_offsets(&self.content);
4113
4114 let backup_node = BackupNode {
4116 node_type: "FunctionRename".to_string(),
4117 identifier: format!("{} -> {}", op.old_name, op.new_name),
4118 original_content: format!("Renamed {} to {}", op.old_name, op.new_name),
4119 location: NodeLocation {
4120 line: 1,
4121 column: 0,
4122 end_line: 1,
4123 end_column: 0,
4124 },
4125 };
4126
4127 Ok(ModificationResult {
4128 changed: true,
4129 modified_nodes: vec![backup_node],
4130 })
4131 }
4132 }
4133 }
4134
4135 fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
4137 if line == 0 || line > self.line_offsets.len() {
4138 anyhow::bail!("Line {} out of range", line);
4139 }
4140
4141 let line_start = self.line_offsets[line - 1];
4142 Ok(line_start + column)
4143 }
4144}
4145
4146struct MatchArmAdder {
4148 target_function: Option<String>,
4149 arm_to_add: Arm,
4150 modified: bool,
4151 current_function: Option<String>,
4152 modified_function: Option<String>,
4153}
4154
4155impl VisitMut for MatchArmAdder {
4156 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4157 let prev_fn = self.current_function.clone();
4158 self.current_function = Some(node.sig.ident.to_string());
4159
4160 syn::visit_mut::visit_item_fn_mut(self, node);
4162
4163 self.current_function = prev_fn;
4164 }
4165
4166 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4167 if let Some(ref target) = self.target_function {
4169 if self.current_function.as_ref() != Some(target) {
4170 syn::visit_mut::visit_expr_match_mut(self, node);
4172 return;
4173 }
4174 }
4175
4176 let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
4178 let already_exists = node.arms.iter().any(|arm| {
4179 arm.pat.to_token_stream().to_string() == pattern_str
4180 });
4181
4182 if !already_exists {
4183 node.arms.push(self.arm_to_add.clone());
4185 self.modified = true;
4186 self.modified_function = self.current_function.clone();
4187 }
4188
4189 syn::visit_mut::visit_expr_match_mut(self, node);
4191 }
4192}
4193
4194struct MatchArmUpdater {
4196 target_function: Option<String>,
4197 pattern_to_match: String,
4198 new_body: syn::Expr,
4199 modified: bool,
4200 current_function: Option<String>,
4201 modified_function: Option<String>,
4202}
4203
4204impl VisitMut for MatchArmUpdater {
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);
4210
4211 self.current_function = prev_fn;
4212 }
4213
4214 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4215 if let Some(ref target) = self.target_function {
4217 if self.current_function.as_ref() != Some(target) {
4218 syn::visit_mut::visit_expr_match_mut(self, node);
4219 return;
4220 }
4221 }
4222
4223 for arm in &mut node.arms {
4225 let pattern_str = arm.pat.to_token_stream().to_string();
4226 let pattern_normalized = pattern_str.replace(" ", "");
4228 let target_normalized = self.pattern_to_match.replace(" ", "");
4229
4230 if pattern_normalized == target_normalized {
4231 arm.body = Box::new(self.new_body.clone());
4232 self.modified = true;
4233 self.modified_function = self.current_function.clone();
4234 break;
4235 }
4236 }
4237
4238 syn::visit_mut::visit_expr_match_mut(self, node);
4239 }
4240}
4241
4242struct MatchArmRemover {
4244 target_function: Option<String>,
4245 pattern_to_remove: String,
4246 modified: bool,
4247 current_function: Option<String>,
4248 modified_function: Option<String>,
4249}
4250
4251impl VisitMut for MatchArmRemover {
4252 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4253 let prev_fn = self.current_function.clone();
4254 self.current_function = Some(node.sig.ident.to_string());
4255
4256 syn::visit_mut::visit_item_fn_mut(self, node);
4257
4258 self.current_function = prev_fn;
4259 }
4260
4261 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4262 if let Some(ref target) = self.target_function {
4264 if self.current_function.as_ref() != Some(target) {
4265 syn::visit_mut::visit_expr_match_mut(self, node);
4266 return;
4267 }
4268 }
4269
4270 let mut index_to_remove = None;
4272 for (i, arm) in node.arms.iter().enumerate() {
4273 let pattern_str = arm.pat.to_token_stream().to_string();
4274 let pattern_normalized = pattern_str.replace(" ", "");
4276 let target_normalized = self.pattern_to_remove.replace(" ", "");
4277
4278 if pattern_normalized == target_normalized {
4279 index_to_remove = Some(i);
4280 break;
4281 }
4282 }
4283
4284 if let Some(index) = index_to_remove {
4285 node.arms.remove(index);
4286 self.modified = true;
4287 self.modified_function = self.current_function.clone();
4288 }
4289
4290 syn::visit_mut::visit_expr_match_mut(self, node);
4291 }
4292}
4293
4294struct MultiMatchArmAdder {
4296 target_function: Option<String>,
4297 arms_to_add: Vec<(String, Arm)>, modified: bool,
4299 current_function: Option<String>,
4300 modified_function: Option<String>,
4301}
4302
4303impl VisitMut for MultiMatchArmAdder {
4304 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4305 let prev_fn = self.current_function.clone();
4306 self.current_function = Some(node.sig.ident.to_string());
4307
4308 syn::visit_mut::visit_item_fn_mut(self, node);
4309
4310 self.current_function = prev_fn;
4311 }
4312
4313 fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4314 if let Some(ref target) = self.target_function {
4316 if self.current_function.as_ref() != Some(target) {
4317 syn::visit_mut::visit_expr_match_mut(self, node);
4318 return;
4319 }
4320 }
4321
4322 for (pattern_str, arm) in &self.arms_to_add {
4324 let already_exists = node.arms.iter().any(|existing_arm| {
4326 existing_arm.pat.to_token_stream().to_string() == *pattern_str
4327 });
4328
4329 if !already_exists {
4330 node.arms.push(arm.clone());
4331 self.modified = true;
4332 self.modified_function = self.current_function.clone();
4333 }
4334 }
4335
4336 syn::visit_mut::visit_expr_match_mut(self, node);
4337 }
4338}
4339
4340struct StructLiteralFieldAdder {
4342 struct_name: String,
4343 field_def: String,
4344 field_name: String,
4345 position: InsertPosition,
4346 path_resolver: Option<PathResolver>,
4347 modified: bool,
4348}
4349
4350impl VisitMut for StructLiteralFieldAdder {
4351 fn visit_expr_mut(&mut self, node: &mut Expr) {
4352 if let Expr::Struct(expr_struct) = node {
4354 let is_match = if let Some(resolver) = &self.path_resolver {
4355 resolver.matches_target(&expr_struct.path)
4357 } else {
4358 if self.struct_name.contains("::") {
4364 if self.struct_name.starts_with("*::") {
4366 let target_name = &self.struct_name[3..]; expr_struct.path.segments.last()
4369 .map(|seg| seg.ident.to_string() == target_name)
4370 .unwrap_or(false)
4371 } else {
4372 let path_str = expr_struct.path.segments.iter()
4374 .map(|seg| seg.ident.to_string())
4375 .collect::<Vec<_>>()
4376 .join("::");
4377 path_str == self.struct_name
4378 }
4379 } else {
4380 expr_struct.path.segments.len() == 1
4382 && expr_struct.path.segments.last()
4383 .map(|seg| seg.ident.to_string())
4384 .as_ref() == Some(&self.struct_name)
4385 }
4386 };
4387
4388 if is_match {
4389 let field_exists = expr_struct.fields.iter().any(|fv| {
4391 fv.member.to_token_stream().to_string() == self.field_name
4392 });
4393
4394 if !field_exists {
4395 let field_value_code = format!("{{ {} }}", self.field_def);
4398 if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
4399 if let Some(new_fv) = expr.fields.first() {
4400 match &self.position {
4402 InsertPosition::First => {
4403 expr_struct.fields.insert(0, new_fv.clone());
4404 self.modified = true;
4405 }
4406 InsertPosition::Last => {
4407 expr_struct.fields.push(new_fv.clone());
4408 self.modified = true;
4409 }
4410 InsertPosition::After(after_field) => {
4411 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4413 fv.member.to_token_stream().to_string() == *after_field
4414 }) {
4415 expr_struct.fields.insert(pos + 1, new_fv.clone());
4416 self.modified = true;
4417 }
4418 }
4419 InsertPosition::Before(before_field) => {
4420 if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4422 fv.member.to_token_stream().to_string() == *before_field
4423 }) {
4424 expr_struct.fields.insert(pos, new_fv.clone());
4425 self.modified = true;
4426 }
4427 }
4428 }
4429 }
4430 }
4431 }
4432 }
4433 }
4434
4435 syn::visit_mut::visit_expr_mut(self, node);
4438 }
4439}
4440
4441struct EnumVariantRenamer {
4443 enum_name: String,
4444 old_variant: String,
4445 new_variant: String,
4446 path_resolver: Option<PathResolver>,
4447 modified: bool,
4448}
4449
4450impl EnumVariantRenamer {
4451 fn rename_path(&mut self, path: &mut syn::Path) {
4461 let segments: Vec<_> = path.segments.iter().collect();
4463 let len = segments.len();
4464
4465 if len >= 2 {
4466 let potential_variant = &segments[len - 1];
4468 let potential_enum = &segments[len - 2];
4469
4470 if potential_enum.ident == self.enum_name
4471 && potential_variant.ident == self.old_variant
4472 {
4473 if let Some(resolver) = &self.path_resolver {
4477 let enum_path = syn::Path {
4479 leading_colon: path.leading_colon,
4480 segments: path.segments.iter()
4481 .take(len - 1)
4482 .cloned()
4483 .collect(),
4484 };
4485
4486 if resolver.matches_target(&enum_path) {
4488 path.segments[len - 1].ident = syn::Ident::new(
4489 &self.new_variant,
4490 path.segments[len - 1].ident.span()
4491 );
4492 self.modified = true;
4493 }
4494 } else {
4495 if len == 2 {
4498 path.segments[1].ident = syn::Ident::new(
4499 &self.new_variant,
4500 path.segments[1].ident.span()
4501 );
4502 self.modified = true;
4503 }
4504 }
4505 }
4506 } else if len == 1 {
4507 if segments[0].ident == self.old_variant {
4509 if self.path_resolver.is_none() {
4513 path.segments[0].ident = syn::Ident::new(
4514 &self.new_variant,
4515 path.segments[0].ident.span()
4516 );
4517 self.modified = true;
4518 }
4519 }
4520 }
4521 }
4522}
4523
4524impl VisitMut for EnumVariantRenamer {
4525 fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
4527 if node.ident == self.enum_name {
4528 for variant in &mut node.variants {
4529 if variant.ident == self.old_variant {
4530 variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
4531 self.modified = true;
4532 }
4533 }
4534 }
4535
4536 syn::visit_mut::visit_item_enum_mut(self, node);
4538 }
4539
4540 fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
4542 match pat {
4543 syn::Pat::TupleStruct(tuple_struct) => {
4544 self.rename_path(&mut tuple_struct.path);
4545 }
4546 syn::Pat::Struct(struct_pat) => {
4547 self.rename_path(&mut struct_pat.path);
4548 }
4549 syn::Pat::Path(path_pat) => {
4550 self.rename_path(&mut path_pat.path);
4551 }
4552 _ => {}
4553 }
4554
4555 syn::visit_mut::visit_pat_mut(self, pat);
4557 }
4558
4559 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4561 match expr {
4562 syn::Expr::Path(expr_path) => {
4563 self.rename_path(&mut expr_path.path);
4564 }
4565 syn::Expr::Call(call) => {
4566 if let syn::Expr::Path(path) = &mut *call.func {
4567 self.rename_path(&mut path.path);
4568 }
4569 }
4570 syn::Expr::Struct(struct_expr) => {
4571 self.rename_path(&mut struct_expr.path);
4572 }
4573 _ => {}
4574 }
4575
4576 syn::visit_mut::visit_expr_mut(self, expr);
4578 }
4579}
4580
4581struct EnumVariantReplacementCollector {
4583 enum_name: String,
4584 old_variant: String,
4585 new_variant: String,
4586 path_resolver: Option<PathResolver>,
4587 replacements: Vec<crate::surgical::Replacement>,
4588}
4589
4590impl EnumVariantReplacementCollector {
4591 fn collect_path_replacement(&mut self, path: &syn::Path) {
4593 let segments: Vec<_> = path.segments.iter().collect();
4594 let len = segments.len();
4595
4596 if len >= 2 {
4597 let potential_variant = &segments[len - 1];
4598 let potential_enum = &segments[len - 2];
4599
4600 if potential_enum.ident == self.enum_name
4601 && potential_variant.ident == self.old_variant
4602 {
4603 let should_rename = if let Some(resolver) = &self.path_resolver {
4607 let enum_path = syn::Path {
4608 leading_colon: path.leading_colon,
4609 segments: path.segments.iter()
4610 .take(len - 1)
4611 .cloned()
4612 .collect(),
4613 };
4614 resolver.matches_target(&enum_path)
4615 } else {
4616 len == 2
4618 };
4619
4620 if should_rename {
4621 let span = potential_variant.ident.span();
4622 let start = span.start();
4623 let end = span.end();
4624
4625 self.replacements.push(crate::surgical::Replacement::new(
4626 start,
4627 end,
4628 self.new_variant.clone(),
4629 ));
4630 }
4631 }
4632 } else if len == 1 && self.path_resolver.is_none() {
4633 if segments[0].ident == self.old_variant {
4635 let span = segments[0].ident.span();
4636 let start = span.start();
4637 let end = span.end();
4638
4639 self.replacements.push(crate::surgical::Replacement::new(
4640 start,
4641 end,
4642 self.new_variant.clone(),
4643 ));
4644 }
4645 }
4646 }
4647}
4648
4649impl<'ast> syn::visit::Visit<'ast> for EnumVariantReplacementCollector {
4650 fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
4651 if node.ident == self.enum_name {
4652 for variant in &node.variants {
4653 if variant.ident == self.old_variant {
4654 let span = variant.ident.span();
4655 let start = span.start();
4656 let end = span.end();
4657
4658 self.replacements.push(crate::surgical::Replacement::new(
4659 start,
4660 end,
4661 self.new_variant.clone(),
4662 ));
4663 }
4664 }
4665 }
4666 syn::visit::visit_item_enum(self, node);
4667 }
4668
4669 fn visit_pat(&mut self, pat: &'ast syn::Pat) {
4670 match pat {
4671 syn::Pat::TupleStruct(tuple_struct) => {
4672 self.collect_path_replacement(&tuple_struct.path);
4673 }
4674 syn::Pat::Struct(struct_pat) => {
4675 self.collect_path_replacement(&struct_pat.path);
4676 }
4677 syn::Pat::Path(path_pat) => {
4678 self.collect_path_replacement(&path_pat.path);
4679 }
4680 _ => {}
4681 }
4682 syn::visit::visit_pat(self, pat);
4683 }
4684
4685 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4686 match expr {
4687 syn::Expr::Path(expr_path) => {
4688 self.collect_path_replacement(&expr_path.path);
4689 }
4690 syn::Expr::Call(call) => {
4691 if let syn::Expr::Path(path) = &*call.func {
4692 self.collect_path_replacement(&path.path);
4693 }
4694 }
4695 syn::Expr::Struct(struct_expr) => {
4696 self.collect_path_replacement(&struct_expr.path);
4697 }
4698 _ => {}
4699 }
4700 syn::visit::visit_expr(self, expr);
4701 }
4702}
4703
4704struct FunctionRenamer {
4706 old_name: String,
4707 new_name: String,
4708 path_resolver: Option<PathResolver>,
4709 modified: bool,
4710}
4711
4712impl FunctionRenamer {
4713 fn rename_ident(&mut self, ident: &mut syn::Ident) {
4715 if ident == &self.old_name {
4716 *ident = syn::Ident::new(&self.new_name, ident.span());
4717 self.modified = true;
4718 }
4719 }
4720
4721 fn matches_target_function(&self, path: &syn::Path) -> bool {
4723 if let Some(resolver) = &self.path_resolver {
4724 resolver.matches_target(path)
4725 } else {
4726 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4728 }
4729 }
4730}
4731
4732impl VisitMut for FunctionRenamer {
4733 fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4734 self.rename_ident(&mut node.sig.ident);
4736 syn::visit_mut::visit_item_fn_mut(self, node);
4737 }
4738
4739 fn visit_impl_item_fn_mut(&mut self, node: &mut syn::ImplItemFn) {
4740 self.rename_ident(&mut node.sig.ident);
4742 syn::visit_mut::visit_impl_item_fn_mut(self, node);
4743 }
4744
4745 fn visit_trait_item_fn_mut(&mut self, node: &mut syn::TraitItemFn) {
4746 self.rename_ident(&mut node.sig.ident);
4748 syn::visit_mut::visit_trait_item_fn_mut(self, node);
4749 }
4750
4751 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4752 match expr {
4753 syn::Expr::Call(call) => {
4754 if let syn::Expr::Path(expr_path) = &mut *call.func {
4756 if self.matches_target_function(&expr_path.path) {
4757 if let Some(last_seg) = expr_path.path.segments.last_mut() {
4758 self.rename_ident(&mut last_seg.ident);
4759 }
4760 }
4761 }
4762 }
4763 syn::Expr::Path(expr_path) => {
4764 if self.matches_target_function(&expr_path.path) {
4766 if let Some(last_seg) = expr_path.path.segments.last_mut() {
4767 self.rename_ident(&mut last_seg.ident);
4768 }
4769 }
4770 }
4771 _ => {}
4772 }
4773 syn::visit_mut::visit_expr_mut(self, expr);
4774 }
4775}
4776
4777struct FunctionReplacementCollector {
4779 old_name: String,
4780 new_name: String,
4781 path_resolver: Option<PathResolver>,
4782 replacements: Vec<crate::surgical::Replacement>,
4783}
4784
4785impl FunctionReplacementCollector {
4786 fn collect_replacement(&mut self, ident: &syn::Ident) {
4788 if ident == &self.old_name {
4789 let span = ident.span();
4790 let start = span.start();
4791 let end = span.end();
4792
4793 self.replacements.push(crate::surgical::Replacement::new(
4794 start,
4795 end,
4796 self.new_name.clone(),
4797 ));
4798 }
4799 }
4800
4801 fn matches_target_function(&self, path: &syn::Path) -> bool {
4803 if let Some(resolver) = &self.path_resolver {
4804 resolver.matches_target(path)
4805 } else {
4806 path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4808 }
4809 }
4810}
4811
4812impl<'ast> syn::visit::Visit<'ast> for FunctionReplacementCollector {
4813 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
4814 self.collect_replacement(&node.sig.ident);
4816 syn::visit::visit_item_fn(self, node);
4817 }
4818
4819 fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
4820 self.collect_replacement(&node.sig.ident);
4822 syn::visit::visit_impl_item_fn(self, node);
4823 }
4824
4825 fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
4826 self.collect_replacement(&node.sig.ident);
4828 syn::visit::visit_trait_item_fn(self, node);
4829 }
4830
4831 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4832 match expr {
4833 syn::Expr::Call(call) => {
4834 if let syn::Expr::Path(expr_path) = &*call.func {
4836 if self.matches_target_function(&expr_path.path) {
4837 if let Some(last_seg) = expr_path.path.segments.last() {
4838 self.collect_replacement(&last_seg.ident);
4839 }
4840 }
4841 }
4842 for arg in &call.args {
4844 syn::visit::visit_expr(self, arg);
4845 }
4846 return;
4848 }
4849 syn::Expr::Path(expr_path) => {
4850 if self.matches_target_function(&expr_path.path) {
4852 if let Some(last_seg) = expr_path.path.segments.last() {
4853 self.collect_replacement(&last_seg.ident);
4854 }
4855 }
4856 }
4857 _ => {}
4858 }
4859 syn::visit::visit_expr(self, expr);
4860 }
4861}
4862
4863fn generate_doc_comment(text: &str, style: &DocCommentStyle) -> String {
4869 match style {
4870 DocCommentStyle::Line => {
4871 text.lines()
4873 .map(|line| {
4874 if line.trim().is_empty() {
4875 "///".to_string()
4876 } else {
4877 format!("/// {}", line)
4878 }
4879 })
4880 .collect::<Vec<_>>()
4881 .join("\n")
4882 }
4883 DocCommentStyle::Block => {
4884 if text.contains('\n') {
4886 let lines = text.lines()
4888 .map(|line| format!(" * {}", line))
4889 .collect::<Vec<_>>()
4890 .join("\n");
4891 format!("/**\n{}\n */", lines)
4892 } else {
4893 format!("/** {} */", text)
4895 }
4896 }
4897 }
4898}
4899
4900fn extract_preceding_comment(content: &str, start_line: usize) -> Option<String> {
4903 if start_line == 0 {
4904 return None;
4905 }
4906
4907 let lines: Vec<&str> = content.lines().collect();
4908 if start_line > lines.len() {
4909 return None;
4910 }
4911
4912 let line_idx = start_line.saturating_sub(1); let mut comment_start = line_idx;
4916 let mut found_any_comment = false;
4917
4918 while comment_start > 0 {
4919 let prev_line = lines[comment_start - 1].trim();
4920
4921 let is_comment = prev_line.starts_with("///")
4923 || prev_line.starts_with("//!")
4924 || prev_line.starts_with("//")
4925 || prev_line.starts_with("/**")
4926 || prev_line.starts_with("/*!")
4927 || prev_line.starts_with("/*")
4928 || (prev_line.starts_with("*") && !prev_line.starts_with("*/"))
4929 || prev_line == "*/";
4930
4931 if is_comment {
4932 comment_start -= 1;
4933 found_any_comment = true;
4934 } else if prev_line.is_empty() && found_any_comment {
4935 comment_start -= 1;
4938 } else {
4939 break;
4940 }
4941 }
4942
4943 if !found_any_comment {
4944 return None;
4945 }
4946
4947 let comment_lines: Vec<String> = lines[comment_start..line_idx]
4949 .iter()
4950 .map(|&line| line.to_string())
4951 .collect();
4952
4953 if comment_lines.is_empty() {
4954 None
4955 } else {
4956 Some(comment_lines.join("\n"))
4957 }
4958}
4959
4960struct TargetFinder {
4962 target_type: String,
4963 target_name: String,
4964 found_position: Option<(usize, String)>, }
4966
4967impl TargetFinder {
4968 fn new(target_type: String, target_name: String) -> Self {
4969 Self {
4970 target_type,
4971 target_name,
4972 found_position: None,
4973 }
4974 }
4975}
4976
4977impl<'ast> syn::visit::Visit<'ast> for TargetFinder {
4978 fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
4979 if self.target_type == "struct" && node.ident.to_string() == self.target_name {
4980 let line = node.struct_token.span.start().line;
4983 self.found_position = Some((line, String::new()));
4984 }
4985 syn::visit::visit_item_struct(self, node);
4986 }
4987
4988 fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
4989 if self.target_type == "enum" && node.ident.to_string() == self.target_name {
4990 let line = node.enum_token.span.start().line;
4992 self.found_position = Some((line, String::new()));
4993 }
4994 syn::visit::visit_item_enum(self, node);
4995 }
4996
4997 fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
4998 if self.target_type == "function" && node.sig.ident.to_string() == self.target_name {
4999 let line = node.sig.fn_token.span.start().line;
5001 self.found_position = Some((line, String::new()));
5002 }
5003 syn::visit::visit_item_fn(self, node);
5004 }
5005}
5006
5007impl RustEditor {
5008 pub fn add_doc_comment_surgical(
5010 &mut self,
5011 target_type: &str,
5012 target_name: &str,
5013 doc_text: &str,
5014 style: &DocCommentStyle,
5015 ) -> Result<ModificationResult> {
5016 use syn::visit::Visit;
5017
5018 let mut finder = TargetFinder::new(
5020 target_type.to_string(),
5021 target_name.to_string(),
5022 );
5023 finder.visit_file(&self.syntax_tree);
5024
5025 if let Some((line_num, _indent)) = finder.found_position {
5026 let line_idx = line_num.saturating_sub(1);
5028
5029 let comment = generate_doc_comment(doc_text, style);
5031
5032 let lines: Vec<&str> = self.content.lines().collect();
5034 if line_idx >= lines.len() {
5035 anyhow::bail!("Target not found at line {}", line_num);
5036 }
5037
5038 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5040
5041 let indent = target_line
5043 .chars()
5044 .take_while(|c| c.is_whitespace())
5045 .collect::<String>();
5046
5047 let mut new_lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
5049
5050 let comment_lines: Vec<String> = comment
5052 .lines()
5053 .map(|line| format!("{}{}", indent, line))
5054 .collect();
5055
5056 for (i, comment_line) in comment_lines.iter().rev().enumerate() {
5058 new_lines.insert(line_idx, comment_line.clone());
5059 }
5060
5061 self.content = new_lines.join("\n");
5063
5064 self.syntax_tree = syn::parse_str(&self.content)
5066 .context("Failed to re-parse after adding comment")?;
5067
5068 Ok(ModificationResult {
5069 changed: true,
5070 modified_nodes: vec![BackupNode {
5071 node_type: target_type.to_string(),
5072 identifier: target_name.to_string(),
5073 original_content: target_line,
5074 location: NodeLocation {
5075 line: line_num,
5076 column: 1,
5077 end_line: line_num,
5078 end_column: target_line_len,
5079 },
5080 }],
5081 })
5082 } else {
5083 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5084 }
5085 }
5086
5087 pub fn update_doc_comment_surgical(
5089 &mut self,
5090 target_type: &str,
5091 target_name: &str,
5092 doc_text: &str,
5093 style: &DocCommentStyle,
5094 ) -> Result<ModificationResult> {
5095 use syn::visit::Visit;
5096
5097 let mut finder = TargetFinder::new(
5099 target_type.to_string(),
5100 target_name.to_string(),
5101 );
5102 finder.visit_file(&self.syntax_tree);
5103
5104 if let Some((line_num, _indent)) = finder.found_position {
5105 let line_idx = line_num.saturating_sub(1);
5107
5108 let lines: Vec<&str> = self.content.lines().collect();
5110 if line_idx >= lines.len() {
5111 anyhow::bail!("Target not found at line {}", line_num);
5112 }
5113
5114 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5116
5117 let indent = target_line
5119 .chars()
5120 .take_while(|c| c.is_whitespace())
5121 .collect::<String>();
5122
5123 let mut doc_comment_start = line_idx;
5125 while doc_comment_start > 0 {
5126 let prev_line = lines[doc_comment_start - 1].trim();
5127 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5128 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5129 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5130 prev_line == "*/" {
5131 doc_comment_start -= 1;
5132 } else {
5133 break;
5134 }
5135 }
5136
5137 let mut new_lines: Vec<String> = Vec::new();
5139
5140 for i in 0..doc_comment_start {
5142 new_lines.push(lines[i].to_string());
5143 }
5144
5145 let comment = generate_doc_comment(doc_text, style);
5147 let comment_lines: Vec<String> = comment
5148 .lines()
5149 .map(|line| format!("{}{}", indent, line))
5150 .collect();
5151
5152 for comment_line in comment_lines {
5153 new_lines.push(comment_line);
5154 }
5155
5156 for i in line_idx..lines.len() {
5158 new_lines.push(lines[i].to_string());
5159 }
5160
5161 self.content = new_lines.join("\n");
5163
5164 self.syntax_tree = syn::parse_str(&self.content)
5166 .context("Failed to re-parse after updating comment")?;
5167
5168 Ok(ModificationResult {
5169 changed: true,
5170 modified_nodes: vec![BackupNode {
5171 node_type: target_type.to_string(),
5172 identifier: target_name.to_string(),
5173 original_content: target_line,
5174 location: NodeLocation {
5175 line: line_num,
5176 column: 1,
5177 end_line: line_num,
5178 end_column: target_line_len,
5179 },
5180 }],
5181 })
5182 } else {
5183 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5184 }
5185 }
5186
5187 pub fn remove_doc_comment_surgical(
5189 &mut self,
5190 target_type: &str,
5191 target_name: &str,
5192 ) -> Result<ModificationResult> {
5193 use syn::visit::Visit;
5194
5195 let mut finder = TargetFinder::new(
5197 target_type.to_string(),
5198 target_name.to_string(),
5199 );
5200 finder.visit_file(&self.syntax_tree);
5201
5202 if let Some((line_num, _indent)) = finder.found_position {
5203 let line_idx = line_num.saturating_sub(1);
5205
5206 let lines: Vec<&str> = self.content.lines().collect();
5208 if line_idx >= lines.len() {
5209 anyhow::bail!("Target not found at line {}", line_num);
5210 }
5211
5212 let target_line = lines[line_idx].to_string(); let target_line_len = target_line.len();
5214
5215 let mut doc_comment_start = line_idx;
5217 while doc_comment_start > 0 {
5218 let prev_line = lines[doc_comment_start - 1].trim();
5219 if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5220 prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5221 (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5222 prev_line == "*/" {
5223 doc_comment_start -= 1;
5224 } else {
5225 break;
5226 }
5227 }
5228
5229 let mut new_lines: Vec<String> = Vec::new();
5231
5232 for i in 0..doc_comment_start {
5234 new_lines.push(lines[i].to_string());
5235 }
5236
5237 for i in line_idx..lines.len() {
5241 new_lines.push(lines[i].to_string());
5242 }
5243
5244 self.content = new_lines.join("\n");
5246
5247 self.syntax_tree = syn::parse_str(&self.content)
5249 .context("Failed to re-parse after removing comment")?;
5250
5251 Ok(ModificationResult {
5252 changed: true,
5253 modified_nodes: vec![BackupNode {
5254 node_type: target_type.to_string(),
5255 identifier: target_name.to_string(),
5256 original_content: target_line,
5257 location: NodeLocation {
5258 line: line_num,
5259 column: 1,
5260 end_line: line_num,
5261 end_column: target_line_len,
5262 },
5263 }],
5264 })
5265 } else {
5266 anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5267 }
5268 }
5269}