1use proc_macro2::Ident;
6use syn::visit_mut::VisitMut;
7
8use crate::ast::RustAST;
9
10#[derive(Debug, Clone)]
12pub struct RenameResult {
13 pub count: usize,
15 pub old_name: String,
17 pub new_name: String,
19}
20
21pub struct Rename;
23
24impl Rename {
25 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 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
60struct 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 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 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
126struct 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 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); 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); 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 assert!(output.contains("let x = 2"));
268 }
269}