有个地方想补充一下,楼主把 ast-grep 描述成"抓代码意图而非字面"基本成立,但"意图"这词其实分两层,容易让人高估它的能耐。
你举的例子——找出所有"打开资源忘了关"的写法——前半句它干得漂亮,后半句是另一回事。ast-grep 的规则引擎本质是基于 tree-sitter 的 pattern matching:你能稳稳把每个 fs.open(...)、fopen(...) 的调用点全捞出来,重命名、换行、缩进全糊弄不了它,这点比 grep 强太多。但"忘了关"是一个控制流/数据流属性,等于要求证明"在所有执行路径上都没有对应的 close 调用"。这属于 taint / reachability analysis 的活儿,更像 Semgrep 的 taint mode,或者 Rust 的 clippy、Python 的 bandit 那种专用 linter 的领地。单靠 AST 模式匹配,能告诉你"这里开了",没法负责任地告诉你"这里一定没关"。
所以实操里我的用法是:拿它做"枚举某种写法 + 批量改写",这部分楼主说得准,体验确实丝滑;但真要当 leak detector,我会再人工过一遍结果,或者跟语义更敏感的工具搭着用。其实
另两个小注记:一是它能力上限受 tree-sitter grammar 质量约束,JS/TS/Go/Python/Rust 这些主流语言覆盖很好,但某些小众 DSL 或很新的语法 parse 不对就会漏。二是"跟 ripgrep 一样快"在中小仓库基本成立,毕竟 tree-sitter 要对每个文件真做 parse,纯文本扫描没这步开销,超大 monorepo 上差距会慢慢显出来,当然日常完全够用。
严格来说
你那个 userList 改 users 的例子挺巧——其实纯标识符重命名 LSP 的 rename 也能做,而且更懂作用域。ast-grep 真正爽的是"结构级"改写,比如把所有 if (x != null) 批量换成 if (x),这种基于模式而非标识符的活儿才是它的主场。你后来拿它做过这类结构重构吗?