🌳 AST 与 CST 理论基础 #ast-cst
- AST(抽象语法树):丢弃括号、分号等细节,只保留语义结构
- CST(具体语法树):完整记录所有字符,包括空格、注释、标点
- ast-grep 内部依赖 CST,支持无损重写,保留原始代码格式
示例:a = b + c 和 a=b+c; 具有相同 AST,但不同 CST
- ast-grep 基于 Tree-sitter 生成语法树,支持多种编程语言
- Named Nodes:有语义的节点(函数调用、变量声明)
- Unnamed Nodes:语法结构节点(括号、逗号、运算符)
元变量默认只匹配 named 节点,可通过 $$ 捕获 unnamed 节点
- 低学习曲线:使用熟悉的代码语法编写模式
- 高精度匹配:基于 AST 结构,避免文本搜索的误匹配
- 无损重写:保留原始代码格式,无需重新格式化
- 跨语言支持:基于 Tree-sitter,支持多种编程语言
填补了文本搜索与专业 AST 分析之间的空白
- 模式基于 Tree-sitter AST,匹配逻辑直接作用在语法节点,而非文本。
- 示例以 JavaScript 书写,但概念同样适配 Rust、Go、Python 等官方支持语言。
- 优先使用简短可读的代码片段表达模式,再辅以规则文件收窄上下文。
牢记:只要目标语言有可用语法树,模式语法即可复用。
- 模式代码
a + 1 会与任意 AST 中形如 a + 1 的子树匹配,包含嵌套表达式。
- 必须提供 可被 tree-sitter 解析 的代码;缺少上下文时使用对象式 pattern。
- 借助 Playground 即时校验 AST 展开与匹配结果。
const b = a + 1;
funcCall(a + 1);
deeplyNested({ target: a + 1 });
无法解析时,改写为对象模式注明 kind/field 可显式提供上下文。
- 格式:
$ + 大写字母/数字/下划线,示例 $META_VAR1、$_。
- 非法示例:
$invalid、$123、$KEBAB-CASE;解析将直接失败。
- 单个元变量匹配 一个 named AST 节点,等价于语法级 wildcard。
为便于复用,建议以语义命名:$CALL、$ARG、$COND 等。
console.log($GREETING) 匹配所有单参数日志调用。
- 不会匹配注释、字符串字面量中的文本,也不会匹配参数数量不符的调用。
- 同一元变量在同一模式中只捕获一次,可与规则层的
constraints 联动做类型校验。
console.log('Hello World');
console
.log('Also matched!');
若需同时校验函数名与参数,可结合 kind 限制或转向规则文件。
$$$ 匹配零个或多个节点,可命名为 $$$ARGS、$$$BODY。
- 常用于参数列表、语句块、对象字段等数量不固定的节点序列。
- 与单元变量不同,多元变量匹配的是 节点数组,在重写时可一次性展开。
console.log(); // $$$ 匹配空列表
console.log('msg'); // 匹配一个节点
console.log(key, val);
在 rewrite 中引用 {{ARGS}} 会原样插入匹配到的节点序列。
console.log($$$ARGS) 允许捕获全部实参,含展开参数。
- 可结合
where 约束判断 ARGS 中元素数量或具体结构。
- 零参数、单参数与多参数会统一落入
$$$ARGS 列表。
console.log(...args);
console.log('debug:', key, value);
在报告阶段可遍历 ARGS,生成更具上下文的信息。
function $FUNC($$$ARGS) { $$$BODY } 同时捕获函数名、参数序列、函数体。
- 空参数/空函数体同样匹配,方便批量筛选无用函数。
- 命名多元变量便于 rewrite 中重排参数或内联函数体。
function foo(bar) { return bar; }
function noop() {}
function add(a, b, c) { return a + b + c; }
结合 constraints 可进一步要求 $FUNC 命名或 BODY 中必须包含特定语句。
- 同名元变量要求匹配内容一致,类似正则的 back-reference。
- 示例
$A == $A 仅匹配自比较表达式,可过滤出恒等或可简化的代码。
- 通过
constraints 进一步检测类型(如 kind: identifier)。
a == a; // ✅
1 + 1 == 1 + 1; // ✅
a == b; // ❌ 不匹配
适用于识别重复调用、重复参数等需要“同一节点”保证的场景。
- 以
$_NAME 命名的元变量不会进入 captures,可在不需要引用时减少开销。
- 即使出现多次,
$_FUNC($_ARG) 允许每次匹配不同节点。
- 适合“只匹配,不复用”的结构,例如只要函数调用参数数量满足即可。
$_FUNC($_ARG);
$_CALL(...$_ARGS);
标记为非捕获后,rewrite 中不可再引用该名称,确保语义清晰。
- 默认仅捕获 named 节点;使用
$$VAR 才能包含 operator、标点等 unnamed 节点。
- 结合 Tree-sitter Named vs Unnamed 概念,需提前查阅语言语法。
- 常见于比较、算术运算中,需要捕获运算符或括号时启用。
若需混合捕获 named/unnamed,可搭配规则层 kind: unnamed 精准限制。
- 当模式无法表达上下文时,使用 rule 文件添加
kind、pattern、constraints、relational 等条件。
- 可以在 YAML 中改用对象模式写法,避免缩进、空格造成的解析歧义。
- 利用 Atomic/Relational Rule 组合更复杂的条件或跨节点匹配。
建议先用 pattern 快速验证,再平移到规则文件中巩固约束。