- 抽象语法树(Abstract Syntax Tree)
在计算机科学中,抽象语法树,或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
- 抽象语法树的用途
代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等。
如: JSlint、JSHint 对代码错误或风格的检查,IDE 的错误提示、格式化、高亮、自动补全、语言转化等等
我们如何将 JavaScript 代码转成 AST 呢,业界有一些成熟的方案,我们可以使用它们来辅助我们理解 AST,开发我们想要的功能。 :::info 不同的团队可能会根据业务需求制作特定的 AST 树。
- ES 标准下的 AST 树
- Babel 自定义的 AST 树
- Uglifyjs 自定义的 AST 树
- …… :::
如何使用 AST 树
我们下面使用 ES 标准下的 AST 树,我们可以使用 ES 提供的工具库:
- esprima:解析 JavaScript 的语法,生成 AST 树
- estraverse:遍历 AST 树
- escodegen:将 AST 树 转成 JavaScript 语法的代码
AST explorer:将代码转成 AST 的在线网站,各种标准的 AST 都有。
esprima
const esprima = require('esprima')
let code = `function ast(){}`
const AST = esprima.parseScript(code)
console.log(AST)
// {
// "type": "Program",
// "body": [
// {
// "type": "FunctionDeclaration",
// "id": {
// "type": "Identifier",
// "name": "ast",
// "range": [
// 9,
// 12
// ]
// },
// "params": [],
// "body": {
// "type": "BlockStatement",
// "body": [],
// "range": [
// 14,
// 16
// ]
// },
// "generator": false,
// "expression": false,
// "async": false,
// "range": [
// 0,
// 16
// ]
// }
// ],
// "sourceType": "module",
// "range": [
// 0,
// 16
// ]
// }
estraverse
:::info estraverse 的使用文档:https://github.com/estools/estraverse/wiki/Usage ::: estraverse 提供了两个方法,可以让我们在遍历过程中修改 AST 树
estraverse.traverse
和estraverse.replace
- 两者的区别:后者会返回修改后的 AST 树,前者不会
上面代码中的estraverse.traverse(AST, options)
estraverse.replace(AST, options)
options
是一个对象,它的可配置的属性如下 ```javascript const options = { // 当进入 AST 某个节点时,会触发这个函数 entry: function (node, parent) { // node 代表当前遍历节点 // parent 是 node 的父节点 }, // 当离开 AST 某个节点时,会触发这个函数 leave: function (node, parent) {
}, // 设置 fallback 为 iteration 时,可以用于遍历用户自定义的 AST fallback: ‘iteration’, // 此外,fallback 还可以设置为函数,决定了 AST 节点的哪些属性可遍历 fallback: function (node) { // 比如返回节点中,不是 arguments 的属性 return Object.keys(node).filter(key => key !== ‘arguments’) }, // 设置 keys,可用于定义自定义 AST 的哪些节点哪些属性可遍历 keys: {} }
下面我们举几个例子来理解一下
javascript // 自定义 AST var tree = { type: ‘TestExpression’, argument: { type: ‘UserType’, value: 20, userLiteral: {type: 'Literal',
value: 1
} }, extended: true }; // 配置 const options = { enter: function (node, parent) { console.log(node) }, // 定义了哪些节点,哪些属性可遍历 keys: { // type 为 TestExpression 的节点可遍历,并且遍历该节点的 argument 属性(如果可遍历的话) TestExpression: [‘argument’], // type 为 UserType 的节点可遍历,并且遍历该节点的 userLiteral 属性(如果可遍历的话) UserType: [‘userLiteral’], } }
- 两者的区别:后者会返回修改后的 AST 树,前者不会
estraverse.traverse(tree, options)
```javascript
// 自定义 AST
var tree = {
type: 'TestExpression',
argument: {
type: 'UserType',
value: 20,
userLiteral: {
type: 'Literal',
value: 1
}
},
extended: true
};
const options = {
enter: function (node, parent) {
console.log(node)
},
fallback: 'iteration' // 所有属性可遍历
}
estraverse.traverse(tree, options)
// 自定义 AST
var tree = {
type: 'TestExpression',
argument: {
type: 'UserType',
value: 20,
userLiteral: {
type: 'Literal',
value: 1
}
},
extended: true
};
const options = {
enter: function (node, parent) {
console.log(node)
},
// 设置所有节点的 useLiteral 属性不可遍历
fallback: function (node) {
return Object.keys(node).filter(key => key !== 'userLiteral')
}
}
estraverse.traverse(tree, options)
escodegen
escodegen 可以将 AST 树 转成 JavaScript 语法的代码
const esprima = require('esprima')
const estraverse = require('estraverse')
const escodegen = require('escodegen')
let source = `function ast(){}`
const AST = esprima.parseScript(source)
const options = {
enter: function (node, parent) {
if (node.type === 'Identifier' && parent.type === 'FunctionDeclaration') {
node.name = node.name.toUpperCase()
}
},
}
estraverse.traverse(AST, options)
const result = escodegen.generate(AST)
console.log(result)
JavaScript Parser
JavaScript Parser 是将 JS 源码转化为抽象语法树的解析器。一般来说每个 JS 引擎都会有自己的抽象语法树格式。
:::info
JavaScript Parser 的发展:
SpiderMonkey 是世界上第一款 JS 引擎,使用 C/C++ 实现,后来 mozilla 在 MDN 公布了 SpiderMonkey 引擎的 Parser API 和 AST 标准。 随着前端的发展,出现了用 JS 实现的 JavaScript Parser。
最早实现的是 esprima,它基于 SpiderMonkey 的 API 标准,用 JS 实现。
在 ES2015 开始进入告诉发展期,espirma 的更新慢并且不支持 JSX 等扩展语法,于是出现了 estree,estree 向下兼容 esprima。
acorn 也是基于 SpiderMonkey 标准,比 esprima 快,体积更小,而且支持插件,所以后续的库和 Parser 基本都基于 acorn。如 Webpack、Babel、espree(esprima ——> acorn)。
Babel 也不完全兼容 EStree Spec,它对一些细节做了扩充和修改。
- Literal 扩展成了 StringLiteral、NumbericLiteral、BooleadnLiteral 等,形成了 Babel Spec 标准
除了 SpiderMonkey 系列外,还有别的 AST Spec 标准,比如 UglifyJS 的 AST、Shift 的 AST,它们自建标准都是由自己的原因的。
如 UglifyJS 的作者说,他想换一个 generotor 的成本太大,而且换结构会使它的 generotor 耗时增加一倍等
:::
常用的 JavaScript Parser
- esprima
- acorn
- shift
- traceur