函数的抽象语法树

1
2
3
function add(a, b){
return a + b
}
  1. 代码块是一个 FunctionDeclaration (函数定义)对象
  2. 分为3块:
    • 一个 id,即 add
    • 两个 params,即[a, b]
    • 一个 body,即 {return a + b}

add:identifier (标志)对象

1
2
3
4
{
name: 'add',
type: 'identifier'
}

params: identifier 数组

1
2
3
4
5
6
7
8
9
10
[
{
name: 'a',
type: 'identifier'
},
{
name: 'b',
type: 'identifier'
}
]

body

BlockStatement (块状域)对象,即 {return a + b}

ReturnStatement (Return 域)对象,即 return a + b

argument:BinaryExpression (二项式)对象,即 a + b

left:identifier a
right:identifier b
operator: +

抽象语法树 JSON

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
{
"type": "Program",
"start": 0,
"end": 216,
"body": [
{
"type": "FunctionDeclaration",
"start": 179,
"end": 215,
"id": {
"type": "Identifier",
"start": 188,
"end": 191,
"name": "add"
},
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 192,
"end": 193,
"name": "a"
},
{
"type": "Identifier",
"start": 195,
"end": 196,
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"start": 197,
"end": 215,
"body": [
{
"type": "ReturnStatement",
"start": 201,
"end": 213,
"argument": {
"type": "BinaryExpression",
"start": 208,
"end": 213,
"left": {
"type": "Identifier",
"start": 208,
"end": 209,
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 212,
"end": 213,
"name": "b"
}
}
}
]
}
}
],
"sourceType": "module"
}

AST 对象文档

recast 螺丝刀

npm i recast -g

astexplorer

astexplorer

词法分析

扫描 scanner

按照预定的规则合并成一个个 tokens 列表,会移除空白符、注释等

语法分析

解释器

将词法分析出来的数组转换成 ,验证语法

解析器 100% 覆盖所有代码结构生成树叫做 CST(具体语法树),AST 不是 100% 与源码匹配

代码转换之babel

解析(parsing) — 将代码字符串转换成 AST抽象语法树
转译(transforming) — 对抽象语法树进行变换操作
生成(generation) — 根据变换后的抽象语法树生成新的代码字符串

操作 AST 重要模块,babel 核心

esprima、estraverse、escodegen

  • esprima 将 js 转换成 AST
1
2
3
const esprima = require('esprima')
let code = 'function fn(){}'
let tree = esprima.parseScript(code)
  • estraverse 遍历和修改 AST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const esprima = require('esprima')
const estraverse = require('estraverse')

let code = 'function fn(){}'

let tree = esprima.parseScript(code)

estraverse.traverse(tree, {
enter(node){
console.log('enter', node.type)
},
leave(){
console.log('leave', node,type)
}
})
  • escodegen 将 AST 转换成 js
1
2
3
const escodegen = require('escodegen')
... estraverse
let res = escodegen.generate(tree)

实现语法转换(babel-core、babel-types)

主要依赖esprima、estraverse、escodegen

plugin-transform-arrow-functions

将箭头函数转换 ES5 语法的函数表达式

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
const babel = require('babel-core')
const types = require('babel-type')

let sumCode = `
const sum = (a,b) => {
return a + b
}
`

// 转化 ES5 插件
let ArrowPlugin = {
// 访问者(访问者模式)
visitor: {
// path 是树的路径
ArrowFunctionExpression(path) {
// 获取树节点
let node = path.node;
// 获取参数和函数体
let params = node.params;
let body = node.body;
// 判断函数体是否是代码块,不是代码块则添加 return 和 {}
if (!types.isBlockStatement(body)) {
let returnStatement = types.returnStatement(body);
body = types.blockStatement([returnStatement]);
}
// 生成一个函数表达式树结构
let func = types.functionExpression(null, params, body, false, false);
// 用新的树结构替换掉旧的树结构
types.replaceWith(func);
}
}
};

babel.transform(sumCode, {
plugins: [ArrowPlugin]
})

babel-code 的 transform(codeString,plugins) 方法将 AST 转换成代码块

codeString:转换前的代码块字符串

plugins:配置项,值为数组,存储修改 babel-code 转换的AST的插件

transform 方法将旧的 AST 处理成新的代码块,返回一个 对象.code 为转换后的代码块字符串

plugins:插件方法必须含有 visitor 属性(固定),值为对象,用于存储修改语法树的方法,方法名严格按照 API

types.functionExpression(null, params, body, false, false):函数名,函数参数(必填)、函数体(必填)、是否generator函数(默认false)、是否为async函数(默认false)

plugin-transform-classes

将 ES6 class 类转换成 ES5 构造函数

[JSCodeshift]

jscodeshift是一个跑codemods的工具。codemod是一段描述AST要转化成什么样的代码。

Prettier

node 类型

(parameter) node:
Identifier | 标识符
SimpleLiteral |
RegExpLiteral |
Program | 程序
FunctionDeclaration | 函数声明
FunctionExpression | 函数表达式
ArrowFunctionExpression | 箭头函数表达式
SwitchCase | switch
CatchClause | case
VariableDeclarator | 可变说明符
ExpressionStatement | 表达式
BlockStatement | 块
EmptyStatement | 空
DebuggerStatement | 调试
WithStatement | with
ReturnStatement | 返回值
LabeledStatement |
BreakStatement | break
ContinueStatement | continus
IfStatement | if
SwitchStatement |
ThrowStatement | throw
TryStatement | try
WhileStatement | while
DoWhileStatement | dowhile
ForStatement | for
ForInStatement | for in
ForOfStatement | for of
VariableDeclaration |
ClassDeclaration | class
ThisExpression | this
ArrayExpression | array
ObjectExpression | object
YieldExpression | yield
UnaryExpression | 一元
UpdateExpression |
BinaryExpression | 二元
AssignmentExpression | 赋值
LogicalExpression | 逻辑
MemberExpression | 成员
ConditionalExpression | 条件
SimpleCallExpression |
NewExpression | new
SequenceExpression | 序列
TemplateLiteral | 模板
TaggedTemplateExpression |
ClassExpression |
MetaProperty |
AwaitExpression | await
Property |
AssignmentProperty |
Super |
TemplateElement |
SpreadElement |
ObjectPattern | 对象模式
ArrayPattern | 数组模式
RestElement |
AssignmentPattern |
ClassBody | 类主题
MethodDefinition | 方法定义
ImportDeclaration |
ExportNamedDeclaration |
ExportDefaultDeclaration |
ExportAllDeclaration |
ImportSpecifier |
ImportDefaultSpecifier |
ImportNamespaceSpecifier |
ExportSpecifier

用途

  • 代码语法检查,风格检查,代码格式化,高亮,错误提示,自动补全等
    JSLint、IDE功能

  • 代码压缩
    UglifyJS2

  • 优化代码
    CommonJS、AMD、CMD代码规范转换
    CoffeeScript、TypeScript、JSX转换为原生JS