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