Skip to main content

rs_hack/
operations.rs

1//! Data types for all refactoring operations: add, remove, rename,
2//! update, transform, and batch. Defines EditMode, BackupNode,
3//! and the operation result types.
4
5use std::path::PathBuf;
6
7use serde::{Deserialize, Serialize};
8
9/// Edit mode for operations - controls how changes are applied to source files
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12#[derive(Default)]
13pub enum EditMode {
14    /// Surgical mode: preserve all formatting, only change specific locations
15    /// This is the recommended default for minimal diffs
16    #[default]
17    Surgical,
18    /// Reformat mode: use prettyplease to reformat the entire file
19    /// Use this if you want consistent formatting across the file
20    Reformat,
21}
22
23impl std::fmt::Display for EditMode {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Self::Surgical => write!(f, "surgical"),
27            Self::Reformat => write!(f, "reformat"),
28        }
29    }
30}
31
32impl std::str::FromStr for EditMode {
33    type Err = String;
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        match s.to_lowercase().as_str() {
37            "surgical" => Ok(Self::Surgical),
38            "reformat" => Ok(Self::Reformat),
39            _ => Err(format!(
40                "Invalid edit mode: {}. Valid values are 'surgical' or 'reformat'",
41                s
42            )),
43        }
44    }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(tag = "type")]
49pub enum Operation {
50    AddStructField(AddStructFieldOp),
51    UpdateStructField(UpdateStructFieldOp),
52    RemoveStructField(RemoveStructFieldOp),
53    AddStructLiteralField(AddStructLiteralFieldOp),
54    AddEnumVariant(AddEnumVariantOp),
55    UpdateEnumVariant(UpdateEnumVariantOp),
56    RemoveEnumVariant(RemoveEnumVariantOp),
57    AddMatchArm(AddMatchArmOp),
58    UpdateMatchArm(UpdateMatchArmOp),
59    RemoveMatchArm(RemoveMatchArmOp),
60    AddImplMethod(AddImplMethodOp),
61    AddUseStatement(AddUseStatementOp),
62    AddDerive(AddDeriveOp),
63    Transform(TransformOp),
64    RenameEnumVariant(RenameEnumVariantOp),
65    RenameFunction(RenameFunctionOp),
66    AddDocComment(AddDocCommentOp),
67    UpdateDocComment(UpdateDocCommentOp),
68    RemoveDocComment(RemoveDocCommentOp),
69    SetStructLiteralBase(SetStructLiteralBaseOp),
70    AddCallArg(AddCallArgOp),
71    UpdateCallArg(UpdateCallArgOp),
72    RemoveCallArg(RemoveCallArgOp),
73}
74
75impl Operation {
76    /// Stable string identifier for this operation, used in run metadata
77    /// and for telemetry. Matches the variant name.
78    pub const fn kind_name(&self) -> &'static str {
79        match self {
80            Self::AddStructField(_) => "AddStructField",
81            Self::UpdateStructField(_) => "UpdateStructField",
82            Self::RemoveStructField(_) => "RemoveStructField",
83            Self::AddStructLiteralField(_) => "AddStructLiteralField",
84            Self::AddEnumVariant(_) => "AddEnumVariant",
85            Self::UpdateEnumVariant(_) => "UpdateEnumVariant",
86            Self::RemoveEnumVariant(_) => "RemoveEnumVariant",
87            Self::RenameEnumVariant(_) => "RenameEnumVariant",
88            Self::AddMatchArm(_) => "AddMatchArm",
89            Self::UpdateMatchArm(_) => "UpdateMatchArm",
90            Self::RemoveMatchArm(_) => "RemoveMatchArm",
91            Self::AddImplMethod(_) => "AddImplMethod",
92            Self::AddUseStatement(_) => "AddUseStatement",
93            Self::AddDerive(_) => "AddDerive",
94            Self::Transform(_) => "Transform",
95            Self::RenameFunction(_) => "RenameFunction",
96            Self::AddDocComment(_) => "AddDocComment",
97            Self::UpdateDocComment(_) => "UpdateDocComment",
98            Self::RemoveDocComment(_) => "RemoveDocComment",
99            Self::SetStructLiteralBase(_) => "SetStructLiteralBase",
100            Self::AddCallArg(_) => "AddCallArg",
101            Self::UpdateCallArg(_) => "UpdateCallArg",
102            Self::RemoveCallArg(_) => "RemoveCallArg",
103        }
104    }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct AddStructFieldOp {
109    pub struct_name: String,
110    pub field_def: String, /* e.g., "new_field: Option<String>" or just "new_field" if
111                            * literal_default is provided */
112    pub position: InsertPosition,
113    #[serde(default)]
114    pub literal_default: Option<String>, /* If provided: tries to add to definition
115                                          * (idempotent), always updates literals */
116    #[serde(default)]
117    pub where_filter: Option<String>, // Optional: filter targets (e.g., "derives_trait:Clone")
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct UpdateStructFieldOp {
122    pub struct_name: String,
123    pub field_def: String, // e.g., "field_name: NewType" (field name is parsed from this)
124    #[serde(default)]
125    pub where_filter: Option<String>, // Optional: filter targets (e.g., "derives_trait:Clone")
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct RemoveStructFieldOp {
130    pub struct_name: String,
131    pub field_name: String, // Name of the field to remove
132    #[serde(default)]
133    pub literal_only: bool, // If true, only remove from struct literals, not the definition
134    #[serde(default)]
135    pub where_filter: Option<String>, // Optional: filter targets (e.g., "derives_trait:Clone")
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct AddStructLiteralFieldOp {
140    pub struct_name: String,
141    pub field_def: String, // e.g., "return_type: None"
142    pub position: InsertPosition,
143    #[serde(default)]
144    pub struct_path: Option<String>, // Optional canonical path (e.g., "crate::types::Rectangle")
145}
146
147/// Add or set the base expression (..expr) on struct literals
148/// e.g., adds `..Default::default()` to `Foo { a: 1 }` → `Foo { a: 1, ..Default::default() }`
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct SetStructLiteralBaseOp {
151    pub struct_name: String,
152    /// The base expression (e.g., "Default::default()" or just "default")
153    /// If "default", expands to "Default::default()"
154    pub base_expr: String,
155    #[serde(default)]
156    pub struct_path: Option<String>, // Optional canonical path (e.g., "crate::types::Rectangle")
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct AddEnumVariantOp {
161    pub enum_name: String,
162    pub variant_def: String, // e.g., "NewVariant" or "NewVariant { x: i32 }"
163    pub position: InsertPosition,
164    #[serde(default)]
165    pub where_filter: Option<String>, // Optional: filter targets (e.g., "derives_trait:Clone")
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct UpdateEnumVariantOp {
170    pub enum_name: String,
171    pub variant_def: String, /* e.g., "UpdatedVariant { new_field: Type }" (variant name parsed
172                              * from this) */
173    #[serde(default)]
174    pub where_filter: Option<String>, // Optional: filter targets (e.g., "derives_trait:Clone")
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct RemoveEnumVariantOp {
179    pub enum_name: String,
180    pub variant_name: String, // Name of the variant to remove
181    #[serde(default)]
182    pub where_filter: Option<String>, // Optional: filter targets (e.g., "derives_trait:Clone")
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct AddMatchArmOp {
187    pub pattern: String,               // e.g., "MyEnum::NewVariant"
188    pub body: String,                  // e.g., "todo!()"
189    pub function_name: Option<String>, // Optional: specific function containing match
190    #[serde(default)]
191    pub auto_detect: bool, // Auto-detect missing enum variants
192    pub enum_name: Option<String>,     // Enum name for auto-detection
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct UpdateMatchArmOp {
197    pub pattern: String,               // Pattern to find (e.g., "MyEnum::Variant")
198    pub new_body: String,              // New body for the arm
199    pub function_name: Option<String>, // Optional: specific function containing match
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct RemoveMatchArmOp {
204    pub pattern: String,               // Pattern to remove (e.g., "MyEnum::Variant")
205    pub function_name: Option<String>, // Optional: specific function containing match
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct AddImplMethodOp {
210    pub target: String,     // e.g., "MyStruct" or "impl MyTrait for MyStruct"
211    pub method_def: String, // Full method definition
212    pub position: InsertPosition,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct AddUseStatementOp {
217    pub use_path: String, // e.g., "std::collections::HashMap"
218    pub position: InsertPosition,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct AddDeriveOp {
223    pub target_name: String,  // Name of struct or enum
224    pub target_type: String,  // "struct" or "enum"
225    pub derives: Vec<String>, // e.g., ["Clone", "Debug", "Serialize"]
226    #[serde(default)]
227    pub where_filter: Option<String>, // Optional: filter targets (e.g., "derives_trait:Clone")
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub enum InsertPosition {
232    First,
233    Last,
234    After(String),  // After named item
235    Before(String), // Before named item
236}
237
238#[derive(Debug, Serialize, Deserialize)]
239pub struct BatchSpec {
240    pub base_path: PathBuf,
241    pub operations: Vec<Operation>,
242}
243
244#[derive(Debug, Serialize, Deserialize, Clone)]
245pub struct NodeLocation {
246    pub line: usize,
247    pub column: usize,
248    pub end_line: usize,
249    pub end_column: usize,
250}
251
252/// Backup of a single AST node before modification
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct BackupNode {
255    pub node_type: String, // "ItemStruct", "ItemEnum", "ItemImpl", "ExprStruct", "ExprMatch"
256    pub identifier: String, // "User", "Status::Draft", "process_event", etc.
257    pub original_content: String, // Original AST node as formatted code
258    pub location: NodeLocation,
259}
260
261/// Result of applying an operation
262#[derive(Debug)]
263pub struct ModificationResult {
264    pub changed: bool,
265    pub modified_nodes: Vec<BackupNode>,
266    /// Unmatched qualified paths (only populated for struct literal operations with simple names)
267    /// Maps fully qualified path to count of instances found but not matched
268    pub unmatched_qualified_paths: Option<std::collections::HashMap<String, usize>>,
269}
270
271/// Result of inspecting/listing AST nodes
272#[derive(Debug, Serialize, Deserialize)]
273pub struct InspectResult {
274    pub file_path: String,
275    pub node_type: String,  // "ExprStruct", "ExprMatch", etc.
276    pub identifier: String, // "Shadow", "Config", etc.
277    pub location: NodeLocation,
278    pub snippet: String, // Formatted code snippet
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub preceding_comment: Option<String>, // Doc comments + regular comments before the node
281}
282
283/// Generic transformation operation
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct TransformOp {
286    pub node_type: String,              // "macro-call", "method-call", etc.
287    pub name_filter: Option<String>,    // Filter by name (e.g., "eprintln")
288    pub content_filter: Option<String>, // Filter by content (e.g., "[SHADOW RENDER]")
289    pub action: TransformAction,        // What to do with matching nodes
290}
291
292/// Actions that can be performed on AST nodes
293#[derive(Debug, Clone, Serialize, Deserialize)]
294#[serde(tag = "type")]
295pub enum TransformAction {
296    Comment,                  // Wrap in // comment
297    Remove,                   // Delete the node entirely
298    Replace { with: String }, // Replace with provided code
299}
300
301/// Rename an enum variant across the codebase
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct RenameEnumVariantOp {
304    pub enum_name: String,   // Name of the enum (e.g., "IRValue")
305    pub old_variant: String, // Current variant name (e.g., "HashMapV2")
306    pub new_variant: String, // New variant name (e.g., "HashMap")
307    #[serde(default)]
308    pub enum_path: Option<String>, /* Optional canonical path (e.g.,
309                              * "crate::compiler::types::IRValue") */
310    #[serde(default)]
311    pub edit_mode: EditMode, // How to apply changes (surgical vs reformat)
312}
313
314/// Rename a function across the codebase
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct RenameFunctionOp {
317    pub old_name: String, // Current function name (e.g., "process_v2")
318    pub new_name: String, // New function name (e.g., "process")
319    #[serde(default)]
320    pub function_path: Option<String>, // Optional canonical path (e.g., "crate::utils::process_v2")
321    #[serde(default)]
322    pub edit_mode: EditMode, // How to apply changes (surgical vs reformat)
323}
324
325/// Add documentation comment to an item
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct AddDocCommentOp {
328    pub target_type: String, // "struct", "enum", "function", "field", "variant"
329    pub name: String,        // Name of the target (e.g., "User", "Status::Draft")
330    pub doc_comment: String, // Documentation text (without /// prefix)
331    #[serde(default)]
332    pub style: DocCommentStyle, // Line (///) or Block (/** */)
333}
334
335/// Update existing documentation comment
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct UpdateDocCommentOp {
338    pub target_type: String, // "struct", "enum", "function", "field", "variant"
339    pub name: String,        // Name of the target
340    pub doc_comment: String, // New documentation text
341}
342
343/// Remove documentation comment from an item
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct RemoveDocCommentOp {
346    pub target_type: String, // "struct", "enum", "function", "field", "variant"
347    pub name: String,        // Name of the target
348}
349
350/// Documentation comment style
351#[derive(Debug, Clone, Serialize, Deserialize)]
352#[serde(rename_all = "lowercase")]
353#[derive(Default)]
354pub enum DocCommentStyle {
355    #[default]
356    Line, // /// or //!
357    Block, // /** */ or /*! */
358}
359
360impl std::str::FromStr for DocCommentStyle {
361    type Err = String;
362
363    fn from_str(s: &str) -> Result<Self, Self::Err> {
364        match s.to_lowercase().as_str() {
365            "line" => Ok(Self::Line),
366            "block" => Ok(Self::Block),
367            _ => Err(format!(
368                "Invalid doc comment style: {}. Valid values are 'line' or 'block'",
369                s
370            )),
371        }
372    }
373}
374
375/// Location of a field in the codebase
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct FieldLocation {
378    pub file_path: String,
379    pub line: usize,
380    pub context: FieldContext,
381}
382
383/// Insert position for call arguments (numeric since args are positional)
384#[derive(Debug, Clone, Serialize, Deserialize, Default)]
385pub enum ArgPosition {
386    /// Insert as first argument
387    First,
388    /// Insert as last argument
389    #[default]
390    Last,
391    /// Insert at specific index (0-based, shifts existing args right)
392    Index(usize),
393}
394
395impl std::fmt::Display for ArgPosition {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        match self {
398            Self::First => write!(f, "first"),
399            Self::Last => write!(f, "last"),
400            Self::Index(i) => write!(f, "index:{}", i),
401        }
402    }
403}
404
405impl std::str::FromStr for ArgPosition {
406    type Err = String;
407
408    fn from_str(s: &str) -> Result<Self, Self::Err> {
409        match s.to_lowercase().as_str() {
410            "first" => Ok(Self::First),
411            "last" => Ok(Self::Last),
412            s if s.starts_with("index:") => {
413                let idx = s[6..]
414                    .parse::<usize>()
415                    .map_err(|_| format!("Invalid index in position: {}", s))?;
416                Ok(Self::Index(idx))
417            }
418            s => {
419                // Try parsing as plain number
420                s.parse::<usize>().map_or_else(
421                    |_| {
422                        Err(format!(
423                            "Invalid arg position: {}. Valid values are 'first', 'last', or 'index:N'",
424                            s
425                        ))
426                    },
427                    |idx| Ok(Self::Index(idx)),
428                )
429            }
430        }
431    }
432}
433
434/// Add an argument to function or method calls
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct AddCallArgOp {
437    /// Name of the function or method to target
438    pub call_name: String,
439    /// Expression to add as argument (e.g., "None", "ctx.clone()", "Default::default()")
440    pub arg_expr: String,
441    /// Where to insert the argument
442    #[serde(default)]
443    pub position: ArgPosition,
444    /// Filter to "function" or "method" calls only (None = both)
445    #[serde(default)]
446    pub call_type: Option<String>,
447    /// Filter call sites by content substring
448    #[serde(default)]
449    pub content_filter: Option<String>,
450}
451
452/// Update an argument at a specific index in function or method calls
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct UpdateCallArgOp {
455    /// Name of the function or method to target
456    pub call_name: String,
457    /// Index of the argument to update (0-based)
458    pub arg_index: usize,
459    /// New expression for the argument
460    pub new_expr: String,
461    /// Filter to "function" or "method" calls only (None = both)
462    #[serde(default)]
463    pub call_type: Option<String>,
464    /// Filter call sites by content substring
465    #[serde(default)]
466    pub content_filter: Option<String>,
467}
468
469/// Remove an argument at a specific index from function or method calls
470#[derive(Debug, Clone, Serialize, Deserialize)]
471pub struct RemoveCallArgOp {
472    /// Name of the function or method to target
473    pub call_name: String,
474    /// Index of the argument to remove (0-based)
475    pub arg_index: usize,
476    /// Filter to "function" or "method" calls only (None = both)
477    #[serde(default)]
478    pub call_type: Option<String>,
479    /// Filter call sites by content substring
480    #[serde(default)]
481    pub content_filter: Option<String>,
482}
483
484/// Context in which a field appears
485#[derive(Debug, Clone, Serialize, Deserialize)]
486#[serde(tag = "type")]
487pub enum FieldContext {
488    StructDefinition {
489        struct_name: String,
490        field_type: String,
491    },
492    EnumVariantDefinition {
493        enum_name: String,
494        variant_name: String,
495        field_type: String,
496    },
497    StructLiteral {
498        struct_name: String,
499    },
500}