use proc_macro2::Ident;
use syn::visit_mut::VisitMut;
use crate::ast::RustAST;
#[derive(Debug, Clone)]
pub struct RenameResult {
pub count: usize,
pub old_name: String,
pub new_name: String,
}
pub struct Rename;
impl Rename {
pub fn apply(ast: &mut RustAST, old_name: &str, new_name: &str) -> RenameResult {
let mut renamer = SymbolRenamer::new(old_name, new_name);
renamer.visit_file_mut(ast.file_mut());
RenameResult {
count: renamer.count,
old_name: old_name.to_string(),
new_name: new_name.to_string(),
}
}
pub fn rename_local_in_fn(
ast: &mut RustAST,
fn_name: &str,
old_name: &str,
new_name: &str,
) -> RenameResult {
let mut renamer = ScopedRenamer::new(fn_name, old_name, new_name);
renamer.visit_file_mut(ast.file_mut());
RenameResult {
count: renamer.count,
old_name: old_name.to_string(),
new_name: new_name.to_string(),
}
}
}
struct SymbolRenamer {
old_name: String,
new_name: String,
count: usize,
}
impl SymbolRenamer {
fn new(old_name: &str, new_name: &str) -> Self {
Self {
old_name: old_name.to_string(),
new_name: new_name.to_string(),
count: 0,
}
}
fn maybe_rename(&mut self, ident: &mut Ident) {
if *ident == self.old_name {
*ident = Ident::new(&self.new_name, ident.span());
self.count += 1;
}
}
}
impl VisitMut for SymbolRenamer {
fn visit_ident_mut(&mut self, ident: &mut Ident) {
self.maybe_rename(ident);
}
fn visit_pat_ident_mut(&mut self, node: &mut syn::PatIdent) {
self.maybe_rename(&mut node.ident);
syn::visit_mut::visit_pat_ident_mut(self, node);
}
fn visit_expr_path_mut(&mut self, node: &mut syn::ExprPath) {
if node.path.segments.len() == 1 {
self.maybe_rename(&mut node.path.segments[0].ident);
}
syn::visit_mut::visit_expr_path_mut(self, node);
}
fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
self.maybe_rename(&mut node.sig.ident);
syn::visit_mut::visit_item_fn_mut(self, node);
}
fn visit_item_struct_mut(&mut self, node: &mut syn::ItemStruct) {
self.maybe_rename(&mut node.ident);
syn::visit_mut::visit_item_struct_mut(self, node);
}
fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
self.maybe_rename(&mut node.ident);
syn::visit_mut::visit_item_enum_mut(self, node);
}
fn visit_type_path_mut(&mut self, node: &mut syn::TypePath) {
if node.path.segments.len() == 1 {
self.maybe_rename(&mut node.path.segments[0].ident);
}
syn::visit_mut::visit_type_path_mut(self, node);
}
}
struct ScopedRenamer {
target_fn: String,
old_name: String,
new_name: String,
count: usize,
in_target_fn: bool,
}
impl ScopedRenamer {
fn new(target_fn: &str, old_name: &str, new_name: &str) -> Self {
Self {
target_fn: target_fn.to_string(),
old_name: old_name.to_string(),
new_name: new_name.to_string(),
count: 0,
in_target_fn: false,
}
}
fn maybe_rename(&mut self, ident: &mut Ident) {
if self.in_target_fn && *ident == self.old_name {
*ident = Ident::new(&self.new_name, ident.span());
self.count += 1;
}
}
}
impl VisitMut for ScopedRenamer {
fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
let was_in_target = self.in_target_fn;
if node.sig.ident == self.target_fn {
self.in_target_fn = true;
}
syn::visit_mut::visit_item_fn_mut(self, node);
self.in_target_fn = was_in_target;
}
fn visit_pat_ident_mut(&mut self, node: &mut syn::PatIdent) {
self.maybe_rename(&mut node.ident);
syn::visit_mut::visit_pat_ident_mut(self, node);
}
fn visit_expr_path_mut(&mut self, node: &mut syn::ExprPath) {
if node.path.segments.len() == 1 {
self.maybe_rename(&mut node.path.segments[0].ident);
}
syn::visit_mut::visit_expr_path_mut(self, node);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rename_local_var() {
let mut ast = RustAST::parse(
r#"
fn main() {
let x = 1;
let y = x + 1;
println!("{}", x);
}
"#,
)
.unwrap();
let result = Rename::apply(&mut ast, "x", "value");
assert_eq!(result.count, 2);
let output = ast.to_string();
assert!(output.contains("let value"));
assert!(!output.contains("let x ="));
}
#[test]
fn test_rename_function() {
let mut ast = RustAST::parse(
r#"
fn foo() {}
fn main() {
foo();
}
"#,
)
.unwrap();
let result = Rename::apply(&mut ast, "foo", "bar");
assert_eq!(result.count, 2);
let output = ast.to_string();
assert!(output.contains("fn bar"));
assert!(output.contains("bar ()") || output.contains("bar()"));
assert!(!output.contains("foo"));
}
#[test]
fn test_rename_struct() {
let mut ast = RustAST::parse(
r#"
struct Point { x: i32, y: i32 }
fn main() {
let p: Point = Point { x: 0, y: 0 };
}
"#,
)
.unwrap();
let result = Rename::apply(&mut ast, "Point", "Vec2");
assert!(result.count >= 2);
let output = ast.to_string();
assert!(output.contains("struct Vec2"));
assert!(!output.contains("Point"));
}
#[test]
fn test_scoped_rename() {
let mut ast = RustAST::parse(
r#"
fn foo() {
let x = 1;
}
fn bar() {
let x = 2;
}
"#,
)
.unwrap();
let result = Rename::rename_local_in_fn(&mut ast, "foo", "x", "renamed");
assert_eq!(result.count, 1);
let output = ast.to_string();
assert!(output.contains("let renamed"));
assert!(output.contains("let x = 2"));
}
}