Skip to main content

ryo_source/ops/
rename.rs

1//! Rename operation.
2//!
3//! Mut operation to rename variables, functions, and other symbols.
4
5use proc_macro2::Ident;
6use syn::visit_mut::VisitMut;
7
8use crate::ast::RustAST;
9
10/// Result of a rename operation.
11#[derive(Debug, Clone)]
12pub struct RenameResult {
13    /// Number of occurrences renamed.
14    pub count: usize,
15    /// Old name.
16    pub old_name: String,
17    /// New name.
18    pub new_name: String,
19}
20
21/// Rename operation for symbols.
22pub struct Rename;
23
24impl Rename {
25    /// Rename all occurrences of a symbol.
26    ///
27    /// This renames:
28    /// - Local variable definitions and uses
29    /// - Function definitions and calls
30    /// - Struct/enum definitions and uses
31    pub fn apply(ast: &mut RustAST, old_name: &str, new_name: &str) -> RenameResult {
32        let mut renamer = SymbolRenamer::new(old_name, new_name);
33        renamer.visit_file_mut(ast.file_mut());
34
35        RenameResult {
36            count: renamer.count,
37            old_name: old_name.to_string(),
38            new_name: new_name.to_string(),
39        }
40    }
41
42    /// Rename a local variable within a specific function.
43    pub fn rename_local_in_fn(
44        ast: &mut RustAST,
45        fn_name: &str,
46        old_name: &str,
47        new_name: &str,
48    ) -> RenameResult {
49        let mut renamer = ScopedRenamer::new(fn_name, old_name, new_name);
50        renamer.visit_file_mut(ast.file_mut());
51
52        RenameResult {
53            count: renamer.count,
54            old_name: old_name.to_string(),
55            new_name: new_name.to_string(),
56        }
57    }
58}
59
60/// Visitor that renames all occurrences of a symbol.
61struct SymbolRenamer {
62    old_name: String,
63    new_name: String,
64    count: usize,
65}
66
67impl SymbolRenamer {
68    fn new(old_name: &str, new_name: &str) -> Self {
69        Self {
70            old_name: old_name.to_string(),
71            new_name: new_name.to_string(),
72            count: 0,
73        }
74    }
75
76    fn maybe_rename(&mut self, ident: &mut Ident) {
77        if *ident == self.old_name {
78            *ident = Ident::new(&self.new_name, ident.span());
79            self.count += 1;
80        }
81    }
82}
83
84impl VisitMut for SymbolRenamer {
85    fn visit_ident_mut(&mut self, ident: &mut Ident) {
86        self.maybe_rename(ident);
87    }
88
89    fn visit_pat_ident_mut(&mut self, node: &mut syn::PatIdent) {
90        self.maybe_rename(&mut node.ident);
91        syn::visit_mut::visit_pat_ident_mut(self, node);
92    }
93
94    fn visit_expr_path_mut(&mut self, node: &mut syn::ExprPath) {
95        // Rename simple paths (single segment, no type args)
96        if node.path.segments.len() == 1 {
97            self.maybe_rename(&mut node.path.segments[0].ident);
98        }
99        syn::visit_mut::visit_expr_path_mut(self, node);
100    }
101
102    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
103        self.maybe_rename(&mut node.sig.ident);
104        syn::visit_mut::visit_item_fn_mut(self, node);
105    }
106
107    fn visit_item_struct_mut(&mut self, node: &mut syn::ItemStruct) {
108        self.maybe_rename(&mut node.ident);
109        syn::visit_mut::visit_item_struct_mut(self, node);
110    }
111
112    fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
113        self.maybe_rename(&mut node.ident);
114        syn::visit_mut::visit_item_enum_mut(self, node);
115    }
116
117    fn visit_type_path_mut(&mut self, node: &mut syn::TypePath) {
118        // Rename type references
119        if node.path.segments.len() == 1 {
120            self.maybe_rename(&mut node.path.segments[0].ident);
121        }
122        syn::visit_mut::visit_type_path_mut(self, node);
123    }
124}
125
126/// Visitor that renames a local variable only within a specific function.
127struct ScopedRenamer {
128    target_fn: String,
129    old_name: String,
130    new_name: String,
131    count: usize,
132    in_target_fn: bool,
133}
134
135impl ScopedRenamer {
136    fn new(target_fn: &str, old_name: &str, new_name: &str) -> Self {
137        Self {
138            target_fn: target_fn.to_string(),
139            old_name: old_name.to_string(),
140            new_name: new_name.to_string(),
141            count: 0,
142            in_target_fn: false,
143        }
144    }
145
146    fn maybe_rename(&mut self, ident: &mut Ident) {
147        if self.in_target_fn && *ident == self.old_name {
148            *ident = Ident::new(&self.new_name, ident.span());
149            self.count += 1;
150        }
151    }
152}
153
154impl VisitMut for ScopedRenamer {
155    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
156        let was_in_target = self.in_target_fn;
157
158        if node.sig.ident == self.target_fn {
159            self.in_target_fn = true;
160        }
161
162        syn::visit_mut::visit_item_fn_mut(self, node);
163
164        self.in_target_fn = was_in_target;
165    }
166
167    fn visit_pat_ident_mut(&mut self, node: &mut syn::PatIdent) {
168        self.maybe_rename(&mut node.ident);
169        syn::visit_mut::visit_pat_ident_mut(self, node);
170    }
171
172    fn visit_expr_path_mut(&mut self, node: &mut syn::ExprPath) {
173        if node.path.segments.len() == 1 {
174            self.maybe_rename(&mut node.path.segments[0].ident);
175        }
176        syn::visit_mut::visit_expr_path_mut(self, node);
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_rename_local_var() {
186        let mut ast = RustAST::parse(
187            r#"
188            fn main() {
189                let x = 1;
190                let y = x + 1;
191                println!("{}", x);
192            }
193            "#,
194        )
195        .unwrap();
196
197        let result = Rename::apply(&mut ast, "x", "value");
198        // println! macro args are not parsed by syn, so only 2 renames (1 def + 1 use in binary expr)
199        assert_eq!(result.count, 2);
200
201        let output = ast.to_string();
202        assert!(output.contains("let value"));
203        assert!(!output.contains("let x ="));
204    }
205
206    #[test]
207    fn test_rename_function() {
208        let mut ast = RustAST::parse(
209            r#"
210            fn foo() {}
211            fn main() {
212                foo();
213            }
214            "#,
215        )
216        .unwrap();
217
218        let result = Rename::apply(&mut ast, "foo", "bar");
219        assert_eq!(result.count, 2); // 1 def + 1 call
220
221        let output = ast.to_string();
222        assert!(output.contains("fn bar"));
223        assert!(output.contains("bar ()") || output.contains("bar()"));
224        assert!(!output.contains("foo"));
225    }
226
227    #[test]
228    fn test_rename_struct() {
229        let mut ast = RustAST::parse(
230            r#"
231            struct Point { x: i32, y: i32 }
232            fn main() {
233                let p: Point = Point { x: 0, y: 0 };
234            }
235            "#,
236        )
237        .unwrap();
238
239        let result = Rename::apply(&mut ast, "Point", "Vec2");
240        assert!(result.count >= 2); // def + uses
241
242        let output = ast.to_string();
243        assert!(output.contains("struct Vec2"));
244        assert!(!output.contains("Point"));
245    }
246
247    #[test]
248    fn test_scoped_rename() {
249        let mut ast = RustAST::parse(
250            r#"
251            fn foo() {
252                let x = 1;
253            }
254            fn bar() {
255                let x = 2;
256            }
257            "#,
258        )
259        .unwrap();
260
261        let result = Rename::rename_local_in_fn(&mut ast, "foo", "x", "renamed");
262        assert_eq!(result.count, 1);
263
264        let output = ast.to_string();
265        assert!(output.contains("let renamed"));
266        // bar's x should be unchanged
267        assert!(output.contains("let x = 2"));
268    }
269}