mcpField
A
ast-grep

ast-grep

github.com/ampcode开发7491 次安装32 周前更新
代码仓库

关于 ast-grep

面向需要按代码结构搜索或改写代码的开发者的 ast-grep 技能,可将自然语言需求转为 AST 规则,并指导测试规则后搜索代码库。

主要功能

  1. 01将自然语言转为 AST 搜索规则
  2. 02编写简单模式与 YAML 规则
  3. 03测试并调试规则
  4. 04按结构搜索代码库
  5. 05使用规则改写代码

适用场景

  1. 01查找含特定结构的函数
  2. 02定位特定参数的函数调用
  3. 03查找缺少错误处理的异步函数
  4. 04按规则批量转换代码

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ampcode/amp-contrib --skill ast-grep

安装后在 Claude Code 中输入/ast-grep确认已加载。

附带文件references/rule_reference.md

详细介绍

ast-grep 代码搜索

概述

此技能可帮助将自然语言查询转换为 ast-grep 规则,以进行结构化代码搜索。ast-grep 使用抽象语法树(AST)模式,根据代码结构而非仅根据文本匹配代码,从而能够在大型代码库中进行强大且精准的代码搜索。

何时使用此技能

在用户遇到以下情况时使用此技能:

  • 需要使用结构匹配搜索代码模式(例如,“找出所有没有错误处理的异步函数”)
  • 想要定位特定的语言结构(例如,“找出所有带有特定参数的函数调用”)
  • 请求需要理解代码结构而不仅是文本的搜索
  • 要求搜索具有特定 AST 特征的代码
  • 需要执行传统文本搜索无法处理的复杂代码查询

通用工作流

按照以下流程帮助用户编写有效的 ast-grep 规则:

第 1 步:理解查询

明确了解用户想要查找的内容。如有需要,请提出澄清问题:

  • 他们要查找什么特定代码模式或结构?
  • 使用哪种编程语言?
  • 是否需要考虑特定的边缘情况或变体?
  • 匹配结果应包含或排除哪些内容?
第 2 步:创建示例代码

编写一个能代表用户想要匹配内容的简单代码片段。将其保存到临时文件中进行测试。

示例: 如果要搜索“使用 await 的异步函数”,请创建一个测试文件:

// test_example.js
async function example() {
	const result = await fetchData()
	return result
}
第 3 步:编写 ast-grep 规则

将模式转换为 ast-grep 规则。从简单规则开始,并根据需要添加复杂性。

关键原则:

  • 对关系规则(inside、has)始终使用 stopBy: end,以确保搜索遍历到方向末端
  • 对简单结构使用 pattern
  • 对复杂结构使用带有 has/inside 的 kind
  • 使用 all、any 或 not 将复杂查询拆分成较小的子规则

示例规则文件(test_rule.yml):

id: async-with-await
language: javascript
rule:
  kind: function_declaration
  has:
    pattern: await $EXPR
    stopBy: end

有关规则的完整文档,请参阅 references/rule_reference.md。

第 4 步:测试规则

使用 ast-grep CLI 验证规则是否能匹配示例代码。主要有两种方法:

选项 A:使用内联规则测试(适用于快速迭代)

echo "async function test() { await fetch(); }" | ast-grep scan --inline-rules "id: test
language: javascript
rule:
  kind: function_declaration
  has:
    pattern: await \$EXPR
    stopBy: end" --stdin

选项 B:使用规则文件测试(复杂规则推荐)

ast-grep scan --rule test_rule.yml test_example.js

未匹配到内容时的调试方法:

  1. 简化规则(移除子规则)
  2. 如果关系规则中没有 stopBy: end,则添加它
  3. 使用 --debug-query 了解 AST 结构(见下文)
  4. 检查 kind 值是否适用于该语言
第 5 步:搜索代码库

规则能够正确匹配示例代码后,即可搜索实际代码库:

对于简单模式搜索:

ast-grep run --pattern 'console.log($ARG)' --lang javascript /path/to/project

对于复杂的基于规则的搜索:

ast-grep scan --rule my_rule.yml /path/to/project

对于内联规则(无需创建文件):

ast-grep scan --inline-rules "id: my-rule
language: javascript
rule:
  pattern: \$PATTERN" /path/to/project

使用 ast-grep 重写代码

ast-grep 是一款强大的基于 AST 的工具,可搜索代码模式并将其转换为新代码。它的作用类似于了解代码结构而不仅是文本的语法感知版 sed/grep。

方法 1:使用 --rewrite 命令行选项

最简单的方法是在终端中直接使用 --rewrite(或 -r)标志:

ast-grep run --pattern 'foo' --rewrite 'bar' --lang python

此命令会查找所有 foo,并将其替换为 bar。以下是一个实际示例:

# Convert old-style property checks to optional chaining
ast-grep -p '$PROP && $PROP()' --rewrite '$PROP?.()' --interactive -l ts ./src

关键标志:

  • --interactive 或 -i:应用前检查每项更改
  • --update-all 或 -U:不经确认应用所有更改
方法 2:使用带有 fix 的 YAML 规则

对于更复杂的转换,请使用包含 fix 字段的 YAML 规则文件:

id: change_def
language: Python
rule:
  pattern: |
    def foo($X):
      $$$S
fix: |-
  def baz($X):
    $$$S

运行命令:ast-grep scan -r rule.yml ./src

元变量

元变量是实现强大重写的关键。它们的作用类似正则表达式中的捕获组:

元变量匹配内容
$NAME任意单个 AST 节点(表达式、标识符等)
$$$ITEMS多个节点(如函数参数)

示例 —— 交换赋值两侧:

rule:
  pattern: $X = $Y
fix: $Y = $X

将 a = b 转换为 b = a。

缩进敏感性

ast-grep 会在重写时保留缩进。如果修复模板中包含缩进,它会相对于原代码位置保留:

rule:
  pattern: '$B = lambda: $R'
fix: |-
  def $B():
    return $R
使用 FixConfig 扩展匹配范围

有时需要删除周围的字符(如逗号)。使用 FixConfig 的 expandStart 和 expandEnd:

rule:
  kind: pair
  has:
    field: key
    regex: Remove
fix:
  template: ''
  expandEnd: { regex: ',' } # Also deletes trailing comma

这会移除匹配的节点以及其后的逗号。

高级功能:重写器

对于涉及多个节点的复杂转换,可以使用 rewriters 处理匹配节点列表:

id: barrel-to-single
language: JavaScript
rule:
  pattern: import {$$$IDENTS} from './module'
rewriters:
  - id: rewrite-identifier
    rule:
      pattern: $IDENT
      kind: identifier
    transform:
      LIB: { convert: { source: $IDENT, toCase: lowerCase } }
    fix: import $IDENT from './module/$LIB'
transform:
  IMPORTS:
    rewrite:
      rewriters: [rewrite-identifier]
      source: $$$IDENTS
      joinBy: "\n"
fix: $IMPORTS

这会将 import { A, B } from './module' 这类聚合导入转换为单独的导入语句。

工作流摘要
  1. 查找:使用模式匹配 AST 节点
  2. 捕获:元变量($VAR、$$$ARGS)捕获匹配内容
  3. 转换:可选地处理捕获的内容(大小写转换、正则替换)
  4. 修补:使用 fix 模板替换匹配的节点
提示
  • 在命令行中使用单引号,防止 shell 展开 $
  • 未匹配的元变量在修复内容中会变为空字符串

ast-grep CLI 命令

检查代码结构(--debug-query)

转储 AST 结构,以了解代码的解析方式:

ast-grep run --pattern 'async function example() { await fetch(); }' \
  --lang javascript \
  --debug-query=cst

可用格式:

  • cst:具体语法树(显示包括标点符号在内的所有节点)
  • ast:抽象语法树(仅显示命名节点)
  • pattern:显示 ast-grep 如何解读你的模式

用途:

  • 查找节点的正确 kind 值
  • 了解要匹配的代码结构
  • 调试模式为何无法匹配

示例:

# See the structure of your target code
ast-grep run --pattern 'class User { constructor() {} }' \
  --lang javascript \
  --debug-query=cst

# See how ast-grep interprets your pattern
ast-grep run --pattern 'class $NAME { $$$BODY }' \
  --lang javascript \
  --debug-query=pattern
测试规则(使用 --stdin 扫描)

无需创建文件,即可针对代码片段测试规则:

echo "const x = await fetch();" | ast-grep scan --inline-rules "id: test
language: javascript
rule:
  pattern: await \$EXPR" --stdin

添加 --json 以获取结构化输出:

echo "const x = await fetch();" | ast-grep scan --inline-rules "..." --stdin --json
使用模式搜索(run)

基于简单模式搜索单个 AST 节点匹配项:

# Basic pattern search
ast-grep run --pattern 'console.log($ARG)' --lang javascript .

# Search specific files
ast-grep run --pattern 'class $NAME' --lang python /path/to/project

# JSON output for programmatic use
ast-grep run --pattern 'function $NAME($$$)' --lang javascript --json .

适用场景:

  • 简单的单节点匹配
  • 无需复杂逻辑的快速搜索
  • 不需要关系规则(inside/has)时
使用规则搜索(scan)

使用基于 YAML 规则的搜索,执行复杂的结构查询:

# With rule file
ast-grep scan --rule my_rule.yml /path/to/project

# With inline rules
ast-grep scan --inline-rules "id: find-async
language: javascript
rule:
  kind: function_declaration
  has:
    pattern: await \$EXPR
    stopBy: end" /path/to/project

# JSON output
ast-grep scan --rule my_rule.yml --json /path/to/project

适用场景:

  • 复杂的结构搜索
  • 关系规则(inside、has、precedes、follows)
  • 组合逻辑(all、any、not)
  • 需要完整 YAML 规则的强大功能时

提示: 对于关系规则(inside/has),请务必添加 stopBy: end,以确保完整遍历。

编写有效规则的提示

始终使用 stopBy: end

对于关系规则,除非有特定原因,否则请始终使用 stopBy: end:

has:
  pattern: await $EXPR
  stopBy: end

这可以确保搜索遍历整个子树,而不是在遇到第一个不匹配的节点时停止。

从简单开始,再逐步增加复杂度

先从可能奏效的最简单规则开始:

  1. 先尝试 pattern
  2. 如果不起作用,再尝试使用 kind 匹配节点类型
  3. 根据需要添加关系规则(has、inside)
  4. 使用组合规则(all、any、not)组合复杂逻辑
选择合适的规则类型
  • Pattern:用于简单、直接的代码匹配(例如 console.log($ARG))
  • Kind + Relational:用于复杂结构(例如“包含 await 的函数”)
  • Composite:用于逻辑组合(例如“包含 await,但不在 try-catch 中的函数”)
通过检查 AST 进行调试

规则无法匹配时:

  1. 使用 --debug-query=cst 查看实际的 AST 结构
  2. 检查元变量是否被正确检测
  3. 确认节点 kind 与预期一致
  4. 确保关系规则沿正确方向搜索
内联规则中的转义

使用 --inline-rules 时,请转义 Shell 命令中的元变量:

  • 使用 \$VAR,而不是 $VAR(Shell 会将 $ 解释为变量)
  • 或使用单引号:'$VAR' 在大多数 Shell 中都可用

示例:

# Correct: escaped $
ast-grep scan --inline-rules "rule: {pattern: 'console.log(\$ARG)'}" .

# Or use single quotes
ast-grep scan --inline-rules 'rule: {pattern: "console.log($ARG)"}' .

常见用例

查找包含特定内容的函数

查找使用 await 的异步函数:

ast-grep scan --inline-rules "id: async-await
language: javascript
rule:
  all:
    - kind: function_declaration
    - has:
        pattern: await \$EXPR
        stopBy: end" /path/to/project
查找特定上下文中的代码

查找类方法中的 console.log:

ast-grep scan --inline-rules "id: console-in-class
language: javascript
rule:
  pattern: console.log(\$\$\$)
  inside:
    kind: method_definition
    stopBy: end" /path/to/project
查找缺少预期模式的代码

查找不含 try-catch 的异步函数:

ast-grep scan --inline-rules "id: async-no-trycatch
language: javascript
rule:
  all:
    - kind: function_declaration
    - has:
        pattern: await \$EXPR
        stopBy: end
    - not:
        has:
          pattern: try { \$\$\$ } catch (\$E) { \$\$\$ }
          stopBy: end" /path/to/project

资源

references/

包含 ast-grep 规则语法的详细文档:

  • rule_reference.md:全面介绍 ast-grep 规则,涵盖原子规则、关系规则、组合规则和元变量

需要了解详细的规则语法时,请加载这些参考资料。

评论

支持 Markdown