4、核心概念:AST(抽象语法树)入门

说实话,很多同学第一次接触 AST 时,都觉得这东西很玄乎。我当年也一样,看到那棵「树」就头大。但后来我发现,搞懂 AST 其实没那么难。你想想看,我们写代码,浏览器怎么理解?它总得有个中间步骤吧。这个中间步骤,就是 AST。

什么是 AST?

AST 的全称是 Abstract Syntax Tree,中文叫「抽象语法树」。说白了,它就是代码的一种树形表示。每个节点代表代码中的一个结构,比如变量声明、函数调用、条件判断等等。

举个例子,你写了一句:

const name = '张三'

在 AST 里,它会被拆成这样:

  • 根节点:VariableDeclaration(变量声明)
  • 子节点:VariableDeclarator(声明器)
  • 子节点的子节点:Identifier(标识符)→ name
  • 子节点的子节点:Literal(字面量)→ '张三'

嗯,这里要注意:AST 只保留「语法结构」,不保留空格、注释、分号这些细节。所以它叫「抽象」语法树,不是「具体」语法树。

核心要点:AST 是代码的结构化表示。它把源代码变成了一棵可遍历、可操作的树。我们做规范检查,本质上就是在这棵树上「找茬」。

为什么用 AST 做规范检查?

你可能要问:直接用正则表达式匹配代码不行吗?我刚开始也这么想。直到我在项目中遇到一个坑——用正则去匹配函数名,结果注释里的函数名也被匹配到了,还误报了十几个错误。从那以后,我再也不敢用正则做代码检查了。

用 AST 做规范检查,有这几个好处:

  • 精准定位:AST 知道每个节点在源代码中的行列位置,报错时能精确到第几行第几列。
  • 语义理解:AST 能区分「变量声明」和「函数调用」,正则做不到这一点。
  • 跨语言通用:JavaScript、TypeScript、CSS、HTML 都有自己的 AST 解析器,思路完全一样。
  • 可组合性:你可以写多个规则,每个规则只关注树上的某类节点,互不干扰。

个人经验:我习惯把 AST 检查比作「体检」。正则就像用肉眼观察,只能看到表面。AST 就像做 CT 扫描,能看到代码的「骨骼」和「内脏」。做规范检查,当然要用 CT 级别的工具。

AST 的结构长什么样?

我们来看一个稍微复杂点的例子。假设有这样一段代码:

function greet(name) {
  return 'Hello, ' + name
}

它的 AST 结构大致如下(简化版):

Program
  └── FunctionDeclaration
      ├── id: Identifier (name: "greet")
      ├── params: [Identifier (name: "name")]
      └── body: BlockStatement
          └── ReturnStatement
              └── BinaryExpression (operator: "+")
                  ├── left: Literal (value: "Hello, ")
                  └── right: Identifier (name: "name")

你看,每个节点都有类型(type),还有属性(比如 name、value、operator)。我们写规范检查规则时,就是遍历这棵树,找到特定类型的节点,然后检查它的属性是否符合规范。

在线 AST 解析工具使用

光说不练假把式。我推荐两个在线工具,你可以直接上手试试:

工具名称 网址 特点
AST Explorer astexplorer.net 支持多种语言、多种解析器,实时展示树结构
TypeScript AST Viewer ts-ast-viewer.com 专门针对 TypeScript,显示节点类型和位置信息

我个人最常用的是 AST Explorer。打开后,左边写代码,右边立刻显示 AST 树。你可以点击树上的节点,左边对应的代码片段会高亮。反过来,选中代码片段,右边对应的节点也会高亮。这个交互设计非常直观。

避坑指南:我曾经在调试一个规则时,怎么都匹配不到目标节点。后来才发现,我用的解析器版本不对。AST Explorer 支持切换解析器(比如 @babel/parser、@typescript-eslint/parser),不同解析器生成的 AST 结构略有差异。写规则前,一定要确认你用的解析器和工具链一致。

AST 的核心节点类型

做规范检查,你至少得认识这几种常见节点:

  • Identifier:标识符,比如变量名、函数名
  • Literal:字面量,比如字符串、数字、布尔值
  • VariableDeclaration:变量声明(const、let、var)
  • FunctionDeclaration:函数声明
  • CallExpression:函数调用表达式
  • MemberExpression:成员访问表达式(比如 obj.prop)
  • BinaryExpression:二元表达式(比如 a + b)
  • IfStatement:if 语句
  • ForStatement:for 循环

每个节点类型都有对应的属性。比如 Identifier 有 name 属性,Literal 有 value 属性。这些属性就是我们写规则时的「检查点」。

注意:AST 节点类型是区分大小写的。比如 FunctionDeclaration 不能写成 functionDeclaration。写规则时拼写错了,规则就不会生效。我刚开始就犯过这个低级错误,排查了半天才发现是大小写问题。

本章知识体系

下面这张图帮你梳理了 AST 的核心概念和它在规范检查中的位置:

AST 知识体系 源代码 解析器(Parser) 抽象语法树(AST) 节点类型 Identifier Literal / CallExpression ... 遍历方式 深度优先遍历 访问者模式(Visitor) 应用场景 规范检查 / 代码格式化 代码转换 / 自动修复

从这张图可以看出,AST 是整个规范检查流程的「中枢」。源代码经过解析器变成 AST,然后我们基于节点类型和遍历方式,去实现各种应用场景。说白了,你只要掌握了 AST,规范检查工具的核心原理就通了。

总结一下:AST 就是把代码变成树,让我们能精确地找到每个语法结构。在线工具是学习 AST 最好的方式,打开 AST Explorer,写几行代码,点一点节点,比看十篇文章都管用。下一节我们会实际动手,用 AST 写一个简单的规范检查规则。


专注资料整理