#语法树 #搜索模式 #抽象语法树 #替换 #规则 #模式匹配 #树搜索

夜间版 bin+lib rerast

基于抽象语法树(AST)的 Rust 代码搜索替换工具

95 个版本

0.1.96 2023年6月5日
0.1.95 2020年5月22日
0.1.93 2020年4月29日
0.1.89 2020年3月5日
0.1.4 2017年11月23日

#224Cargo 插件

Apache-2.0

265KB
6K SLoC

Rerast

Rerast 是一个基于规则搜索替换的 Rust 代码工具。一个规则由一个搜索模式、一个替换和一个可选的占位符组成,该占位符可以出现在搜索模式和替换中。匹配是在语法上进行的,而不是在文本上,因此格式不重要。占位符是类型化的,并且必须与代码中找到的类型匹配,才能应用规则。

Rerast 已弃用。我们建议使用 rust-analyzer 中的结构化搜索替换功能,该功能可在 vscode 或命令行(以及可能还有 vim)中使用。如果您缺少 Rerast 支持的任何特定功能,请在此处评论 此问题

安装

rustup toolchain add nightly-2020-02-27
rustup component add --toolchain nightly-2020-02-27 rustc-dev
cargo +nightly-2020-02-27 install --version 0.1.88 rerast

使用

基本操作可以从命令行完全执行

cargo +nightly-2020-02-27 rerast --placeholders 'a: i32' --search 'a + 1' --replace_with 'a - 1' --diff

或者您可以将规则放入一个 Rust 文件中

fn rule1(a: i32) {
  replace!(a + 1 => a - 1);
}

然后使用

cargo +nightly-2020-02-27 rerast --rules_file=my_rules.rs

如果您想一次性应用多个规则,则必须在文件中放置您的规则。

如果您想实际更新您的文件,可以按以下方式操作

cargo +nightly-2020-02-27 rerast --placeholders 'a: i32' --search 'a + 1' --replace_with 'a - 1' --force --backup

您可以使用 --file 参数控制 rerast 将规则注入到哪些编译根中,例如

cargo +nightly-2020-02-27 rerast --rules_file=my_rules.rs --targets tests --file tests/testsuite/main.rs --diff

这是一个更复杂的示例

use std::rc::Rc;
fn rule1<T>(r: Rc<T>) {
  replace!(r.clone() => Rc::clone(&r))
}

在这里,我们用更明确的方式克隆 Rc 上的 clone() 方法调用替换为通过 Rc::clone 的克隆。

"r" 是一个占位符,它将匹配指定类型的任何表达式。函数 "rule1" 的名称目前没有实际用途。在未来,可能可以通过指定名称来选择性地启用/禁用规则,所以在这里放一个稍微描述性的名称可能是个好主意。同样,放在函数之前的注释可能在将来显示给用户,当规则匹配时。这尚未实现。

函数可以包含多个replace!宏的调用,较早的规则具有优先权。如果您想进行多个替换,这些替换使用相同的占位符,或者您想首先处理某些特殊模式,然后再进行更一般的匹配,这非常有用。

除了replace!之外,还有其他几个可以使用的替换宏。

  • replace_pattern! - 这用于替换模式。例如 &Some(a)。这样的模式可能出现在匹配分支或if let中。不可反驳的模式(那些始终可以匹配的模式)也可以在let语句和函数参数中进行匹配。
  • replace_type! - 这用于替换类型。目前它有一些限制,因为它不支持占位符。另外,如果您的类型只是一个trait,您应该考虑使用replace_trait_ref!,因为trait引用可以出现在类型无法出现的上下文中——具体来说是在泛型界限和where子句中。
  • replace_trait_ref! - 这用于替换对命名trait的引用

替换语句目前处于禁用状态,等待找到良好的使用场景。

匹配宏调用

只要宏调用展开成可以匹配的代码,就可以匹配宏调用。然而,请注意,宏调用不会与等效代码匹配,也不会与不同但相同的宏的调用匹配。这是故意的。在验证匹配时,我们检查是否遵循了相同的展开序列。另外,如果宏在每次调用时都展开成不同的内容,它将永远不会匹配。println!就是这样一种宏,因为它生成一个常量,该常量被展开的代码引用,并且每次调用都引用不同的常量。

操作顺序

假设您要将foo(a, b)替换为a && !b。根据占位符的匹配结果以及整个表达式的上下文,可能需要额外的括号。例如,如果匹配的代码是!foo(x == 1, y == 2),如果我们没有添加任何括号,我们最终会得到!x == 1 && !y == 2,这显然是不正确的。Rerast会检测到这一点,并在需要时添加括号,以保留替换中找到的顺序或优先级。这将给出!(x == 1 && !(y == 2))。

代码格式化

目前不进行代码重排。未匹配的代码将不受影响。替换代码是通过从规则中复制替换代码并将匹配的模式拼接进来产生的。在未来,我们可能会调整多行替换的缩进。之后运行rustfmt可能是一个好主意,因为一些缩进和行长度可能不太理想。

递归和重叠匹配

第一个匹配的规则获胜。当某些代码被匹配时,不会对那段代码应用后续的规则。但是,匹配到占位符的代码将搜索所有规则的进一步匹配。

从源代码更改自动确定规则

如果您即将在源代码的多个地方进行更改,并且您使用git,您可以将更改(或暂存)提交,进行一次编辑,然后运行

cargo +nightly-2020-02-27 rerast --replay_git --diff

这将找到项目中更改的表达式(应该只有一个),然后尝试确定会产生这个更改的规则。它将打印规则,然后将其应用于您的项目。如果您对更改满意,可以再次运行并使用--force应用它们,或者您可以复制打印的规则到.rs文件中,并使用--rules_file应用它。

  • 生成的规则将尽可能多地使用占位符。也就是说,如果子表达式在旧代码和新代码中都找到,它将被占位符替换。
  • 目前这仅适用于更改的表达式,不适用于语句、类型、模式等。
  • 您的代码必须能够在更改和未更改的情况下编译。

限制

  • 使用语句尚未更新,因此根据您的规则,在应用规则后可能需要更新。这最终应该会得到解决,只是在发布之前没有时间,而且有点棘手。
  • 您的代码必须能够编译才能使这生效。
  • 替换的代码也必须编译。这意味着rerast在用其非过时的等效项替换已弃用的API使用方面比处理破坏性更改更好。通常,最好的解决方案是临时创建一个新的API。
  • rustdoc中的代码尚未处理和匹配。
  • 使用cfg属性禁用的条件代码不会被匹配。建议尽可能在运行时启用所有功能,以便尽可能多地检查代码。
  • replace_type!目前不支持占位符。
  • 可能存在许多错误和缺失的功能。请随时提交错误/功能请求。

已知问题

  • 如果你在你的项目中有一个集成测试(一个"tests"目录),可能没有匹配。不确定原因。这始于2020年4月10日的nightly版本。你可能可以通过向cargo rerast传递--targets ''来解决这个问题。遗憾的是,这样你将无法在非集成测试(即cfg(test))中找到匹配。或者,你可以安装较旧的rust和相应的rerast版本。
  • 在替换中使用?目前是错误的。我想这发生在2020年2月。spans发生了变化。

更多示例

请参阅Rerast食谱以获取更多示例。

有问题?

请随时在github上提交问题。

作者

请参阅Cargo.toml

贡献

请参阅CONTRIBUTING.md

行为准则

本项目遵循Rust行为准则。如果你觉得有人没有遵守本项目相关的行为准则,请联系David Lattimore。我的电子邮件地址在Cargo.toml中。

免责声明

这不是一个官方的Google产品。Google之所以发布它,只是因为(原始)作者恰好在那里工作。

依赖项

~1.4–8.5MB
~57K SLoC