ESTree

JavaScript AST (Abstract Syntax Tree) 是将 JavaScript 代码转换为抽象语法树的一种表示形式。它可以用来分析、转换和验证 JavaScript 代码。

EStree 是一种用于描述 JavaScript 代码抽象语法树的规范。它定义了 AST 节点类型和属性,以及节点间的关系。EStree 规范是由 ECMAScript 标准委员会制定的,旨在成为 JavaScript AST 的标准表示形式。

EStree 规范定义了许多节点类型,包括表达式、语句、声明等。每个节点类型都有其独特的属性和方法,用于描述 JavaScript 代码的结构和逻辑。通过 EStree 规范,我们可以构建出 JavaScript 代码的完整抽象语法树,从而对代码进行分析、转换和验证。

EStree 规范已经在许多 JavaScript 工具中得到了广泛应用,如 ESLint、Babel 等。理解 EStree 规范对于深入理解 JavaScript AST 和相关工具的实现非常重要。

ESTree 中的不同节点

所有节点类型都实现以下接口:

1
2
3
4
5
interface Node {
type: string;
range?: [number, number];
loc?: SourceLocation;
}

该type字段是表示AST变体类型的字符串。该loc字段表示节点的源位置信息。如果解析器没有生成有关节点源位置的信息,则该字段为null;否则它是一个对象,包括一个起始位置(被解析的源区域的第一个字符的位置)和一个结束位置.

Programs

表示一个完整的源代码树。

1
2
3
4
5
interface Program <: Node {
type: "Program";
sourceType: 'script' | 'module';
body: StatementListItem[] | ModuleItem[];
}

其他节点结构

VariableDeclarator:变量声明

1
2
3
4
5
interface VariableDeclaration <: Declaration {
type: "VariableDeclaration";
declarations: [ VariableDeclarator ];
kind: "var" | "let" | "const";
}

FunctionDeclaration: 函数声明

1
2
3
4
5
6
7
8
9
10

interface FunctionDeclaration {
type: 'FunctionDeclaration';
id: Identifier | null;
params: FunctionParameter[];
body: BlockStatement;
generator: boolean;
async: boolean;
expression: false;
}
  • ClassDeclaration
  • DoWhileStatement
  • ExpressionStatement

表达式语句,即,由单个表达式组成的语句。

1
(function(){});

生成、遍历ESTree

可以借助Babel,Acron等工具进行操作

Acorn 解析器

1
2
const acron = require("acorn");
const ast = acron.parse(data, {});

acron 还可以增加插件支持其他语法的解析,例如jsx:

1
2
3
4
5
6
7
const acron = require("acorn");
const walk = require("acorn-walk");
const ast = acron.Parser.extend(jsx()).parse(data, {
plugin: {
jsx: true,
},
});

Babel

babel 是平常非常常见的用来辅助前端的工具

1
2
const babel = require("@babel/core");
result = babel.transformSync(code2, { ast: true });

除了直接引入core外,可以只引入parser包进行解析:

1
2
3
const astCode = babelParser.parse(data, {
plugins: ["jsx"],
});

babel 还有@babel/traverse 这个库来作为遍历器,遍历estree中的每个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const babelTraverse = require("@babel/traverse");
babelTraverse.default(astCode, {
enter(path) {
// console.log(path.node, "path.node");
if (path.isJSXText()) {
wxmlCode += path.node.value;
}
if (path.isJSXOpeningElement()) {
wxmlCode += `<${transformTagName(path.node.name.name)}>`;
}
if (path.isJSXClosingElement()) {
wxmlCode += `</${transformTagName(path.node.name.name)}>`;
}
},
});

在这个例子中,babelTraverse的default方法第二个参数是一个遍历器,会遍历estree中的每一个节点,以下是其中的一些方法

  1. enter(path): 当遍历器进入某个节点时,会调用该方法。path 是表示当前节点的路径对象,你可以在这里执行与节点进入相关的操作。

  2. exit(path): 当遍历器离开某个节点时,会调用该方法。path 是表示当前节点的路径对象,你可以在这里执行与节点离开相关的操作。

  3. Identifier(path): 这是一个示例方法名,与节点类型对应。你可以根据不同的节点类型来定义方法。比如,在这个例子中,Identifier 方法用于处理标识符节点。

  4. BinaryExpression(path): 与二元表达式节点对应的方法。在这里可以处理二元表达式节点的内容。

  5. FunctionDeclaration(path): 与函数声明节点对应的方法。在这里可以处理函数声明节点的内容。

  6. BlockStatement(path): 与代码块语句节点对应的方法。在这里可以处理代码块语句节点的内容。