抽象语法树(AST)浅析

简介

我们熟知的 Babel, ESlint 和 Prettier 都是基于 AST 的,那么 AST 到底是什么呢?

举个例子:

1
var answer = 6 * 7;

转换为 AST 后是这样的在线查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "answer",
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 10
}
}
},
"init": {
"type": "BinaryExpression",
"operator": "*",
"left": {
"type": "Literal",
"value": 6,
"raw": "6",
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 14
}
}
},
"right": {
"type": "Literal",
"value": 7,
"raw": "7",
"loc": {
"start": {
"line": 1,
"column": 17
},
"end": {
"line": 1,
"column": 18
}
}
},
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 18
}
}
},
"loc": {
"start": {
"line": 1,
"column": 4
},
"end": {
"line": 1,
"column": 18
}
}
}
],
"kind": "var",
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 19
}
}
}
],
"sourceType": "script",
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 19
}
}
}

再看一个:

1
2
3
function add(a, b) {
return a + b;
}

转换为 AST 后是这样的在线查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "add",
"loc": {
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 12
}
}
},
"params": [
{
"type": "Identifier",
"name": "a",
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 14
}
}
},
{
"type": "Identifier",
"name": "b",
"loc": {
"start": {
"line": 1,
"column": 16
},
"end": {
"line": 1,
"column": 17
}
}
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Identifier",
"name": "a",
"loc": {
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 12
}
}
},
"right": {
"type": "Identifier",
"name": "b",
"loc": {
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 16
}
}
},
"loc": {
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 16
}
}
},
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 16
}
}
}
],
"loc": {
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 3,
"column": 1
}
}
},
"generator": false,
"expression": false,
"async": false,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
}
}
],
"sourceType": "script",
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
}
}

如果把 JavsScript 看作一台机器,那么 AST 就是描述这台机器构成的最小单元集合,将这些最小单元通过一定的方式衔接在一起,这个台机器就能运转

应用

基于以上的象形解释,不难看到 AST 就是一棵标准的语法树结构,遍历树节点我们就可以修剪这个棵树, 最终将修剪后得树交给编译器。比如通过 recast 我们就可以轻松操纵 AST 以达到我们的意愿

可以预见,基于 AST 我们可以做到以下事情:

  • 静态分析工具

    比如 ESLint,可以格式化,可以检查代码漏洞

  • 元资产,基于元资产可以做文档输出,做可视化输出,做数据库存储并二次消费等

    比如typescript-doc-gen可以快速将 TSX interface 转成 Markdown,
    documentation使用 babel 输出漂亮的多格式文档,AST Query让你想查询数据库一样操作 AST

参考

Babylon AST node types

揭秘 Prettier

AST 实战

Espree——JavaScript 解释器

Acorn——JavaScript 解释器

在线查看 AST

babel-core

recast—AST 手术刀

The Super Tiny Compiler

AST Explorer

Babel 使用全景图

typescript-doc-gen

documentation

AST Query