这个工具让你无需 AI 就能进行神奇的代码更改
在编程的世界里,文本是王,所以像 grep、sed 和 awk 这样的工具是你编译器或解释器的绝佳伙伴。每个程序员的工具箱也可以从基于文本的版本控制系统中受益,比如 Git。
但这些工具都在通用文本层面工作。虽然正则表达式可以非常强大,但它们仍然不完全适合编程语言的语法。要进行有意义的语义搜索,你需要一种不同类型的工具。于是出现了 ast-grep。
什么是 ast-grep,为什么你需要它?
超级强大的搜索,能够理解你的代码
假设你想要你代码库中所有函数名称的列表。第一次尝试可能看起来像这样:
grep -r 'function' ...
然后你可能会意识到自己匹配了太多无关的情况,所以你会改进你的正则表达式:
egrep -r '^function\W' ...
你会以这种方式继续下去,直到最终你意识到,无论你做什么,你的搜索都会匹配类似于:
/*
The following should be:
function foo(a, b, c)
but that’s not going to work because...
*/
像 grep 这样的工具根本不能理解看起来像是在注释中的函数声明的上下文。它们的工作方式只是匹配单个字符,而不是考虑这些字符组合可能包含的更广泛的意义。
ast-grep 不同。它不是逐字符、逐行地读取文本,而是解析文本(就像编译器一样)并构建抽象语法树(AST),它表示代码的实际含义。利用这一点,它可以进行语义搜索,比如查找变量声明、函数调用等元素。
在底层,ast-grep 使用了流行的 Tree-sitter 库,这使得它能够支持从 Python、Java 到 Go 等各种不同的语言。ast-grep 是用 Rust 编写的,因此性能良好,即使是在大型代码库中搜索也表现出色。这一点很重要,因为它必须在搜索文件之前完全解析文件,这比标准的基于文本的搜索需要做更多的工作。
如何使用 ast-grep 搜索几乎所有内容
使用命令行或适合初学者的网页应用
ast-grep 最简单的用法是使用 -p 选项来指定一个简单的单一模式。
ast-grep -p 'console.log'
如果没有任何路径参数,ast-grep 将在当前目录中递归搜索文件。除非你指定语言,否则 ast-grep 将根据文件的扩展名来推断语言。程序会按文件对结果进行分组,打印每个匹配的详细信息,并突出显示相关部分:
请注意,此搜索返回没有参数的函数调用以及对方法属性的引用,即使它甚至不是一次调用。然而,它特别寻找名为 console 的对象,所以它不会找到以下内容:
with (console) {
log("Hello, world.");
}
但它会正确地忽略作为字符串的 "'console.log'" 和作为注释的 "/* console.log */"。在这一点上,我建议你探索 ast-grep playground,这是一个在后台运行 ast-grep 并展示其结果的网页应用程序:
正如你所看到的,游乐场清楚地显示了哪些匹配了,哪些没有匹配。这与命令行程序形成了对比,命令行程序会过滤输入并只返回匹配项。我发现在刚开始学习 ast-grep 并尝试构建自己的模式时,游乐场非常有用。
您可以使用元变量来匹配更动态的内容。元变量以 $开头,只使用大写字母,以及下划线和数字。要查找所有带有单个参数的 console.log 调用,请使用以下模式:
console.log($SINGLE_ARG)
请注意,该模式匹配带有单个参数的调用,而不匹配没有参数或参数多于一个的调用:
如果你想匹配多个参数,你可以使用多重元变量,它以三个美元符号开始:
console.log($$$MULTIPLE_ARGS)
此模式将匹配对 console.log 的所有调用,包括没有参数或有多个参数的情况:
对于更复杂的搜索,ast-grep 支持 YAML 中的规则语法。你可以使用它来定义高度上下文相关的搜索,从而充分利用 AST 结构。
例如,这里有一条规则,它匹配所有对 console.log、console.debug 和 console.warn 的调用,以及那些不在 catch 语句块内的 console.error 调用:
id: no-console-except-error
language: typescript
rule:
any:
- pattern: console.error($$$)
not:
inside:
kind: catch_clause
stopBy: end
- pattern: console.$METHOD($$$)
constraints:
METHOD:
regex: 'log|debug|warn'
在不手动编辑的情况下更改你的代码
通过仔细的准备和测试,ast-grep 可以成为一个强大的自动化编辑器
ast-grep 已经是一个强大的工具,但它不仅仅是搜索工具。通过其替换功能,ast-grep 还可以让你修改代码。这意味着你可以在几秒钟内完成非常耗时的更改,无论是自动的还是交互式的。
考虑之前的 console.log 示例。想象一下你的某个代码库充斥着调用 console.log 的调试代码。你决定想要一些更复杂的东西,所以你从一个定制的函数 my_logger 开始:
function my_logger() {
if (GLOBAL_DEBUG) {
console.log.apply(null, arguments)
}
}
它只是一个简单的 console.log 包装器,目前带有一个全局标志。但关键是你需要将每个 console.log() 调用转换为 my_logger 调用。使用 ast-grep,这是一个简单的过程。首先创建一个规则文件,例如,fix-logging.yml:
rule:
pattern: console.log($$$MULTIPLE_ARGS)
fix: my_logger($$$MULTIPLE_ARGS)
然后像这样运行它以查看将要进行的更改:
ast-grep scan --rule fix-logging.yml
如果你对结果感到满意,你可以让 ast-grep 为你做这些更改,使用 --update-all 选项直接更新文件:
ast-grep scan --rule fix-logging.yml --update-all
你可以使用多种模式进行更改,包括一种交互式模式,这可能是在你熟悉 ast-grep 时运行它的最安全方式。
尽管学习曲线陡峭,掌握 ast-grep 将会有所回报
毫无疑问:ast-grep 是一个复杂的程序。我在这里包含的示例属于较容易的范围,如果你进一步深入文档,你会看到这个工具还有更多功能可用。
虽然可能让人望而生畏,但 playground 帮助很大,使用交互模式清理你的代码库是开始学习 ast-grep 的一个很好的方式。









