Esprima 是一个用于教育目的的 ECMAScript(JavaScript) 解析架构,主要用于多用途分析。其本身也是使用 ECMAScript 编写的。
主要特性- 支持 ECMAScript 5.1
- 抽象语法树 (AST) 敏感的格式,兼容 Mozilla Parser API
- 经过重度测试,超过 500 个单元测试以及 100% 的代码覆盖
- 可选跟踪语法节点定位 (index-based and line-column)
- 超级快,速度是 UglifyJS parse-js 的 2.5 倍(speed comparison)
- Esprima 是一个用于对 JS 代码做词法或者语法分析的工具
- 体验网址
- 只支持js,不支持 flow 或者 typescript 格式
-
当前最新版本是4.0,主要提供两个API:
- parseScript 解析不包含 import 和 export 之类的js 代码
- parseModule 解析含 import 和 export 之类的js 代码
- 4.0 以下的版本仅支持 parse 方法,需自行判断是 script 还是 module
- 语法格式
esprima.parseScript(input, config, delegate) esprima.parseModule(input, config, delegate)
input 代表原始 js 字符串 config 是如下的配置对象:
config
delegate参数
// node 包含节点类型等信息,metadata 包含该节点位置等信息 function (node, metadata) { console.log(node.type, metadata); }进阶
Esprima 是用来做词法和语法分析的,这需要对其解析之后的对象结构有清楚的了解,本节分析 Esprima 解析后生成的语法结构树。
总体结构语法树的总体结构就两种
interface Program { type: 'Program'; sourceType: 'script'; body: StatementListItem[]; } interface Program { type: 'Program'; sourceType: 'module'; body: ModuleItem[]; }
StatementListItem && ModuleItem 其中 ModuleItem(模块项)只是比 StatementListItem(变量声明和执行语句列表项)多了导入和导出两个module才会用到的类型,这两个类型用的少,所以只用关心 StatementListItem
type StatementListItem = Declaration | Statement; type ModuleItem = ImportDeclaration | ExportDeclaration | StatementListItem;
从 StatementListItem 可看出其只包含 Declaration(变量声明) 和 Statement(执行语句)
枚举 Declaration
type Declaration = ClassDeclaration | FunctionDeclaration | VariableDeclaration;
声明包括:类声明、函数声明、变量声明
枚举 Statement
type Statement = BlockStatement | BreakStatement | ContinueStatement | DebuggerStatement | DoWhileStatement | EmptyStatement | ExpressionStatement | ForStatement | ForInStatement | ForOfStatement | FunctionDeclaration | IfStatement | LabeledStatement | ReturnStatement | SwitchStatement | ThrowStatement | TryStatement | VariableDeclaration | WhileStatement | WithStatement;
执行语句包括:块、break、continue、debugger、do while、空语句、表达式语句、for、for in、for of、function、if、标签、return、switch、throw、try、var、while、with。
其中 ExpressionStatement 比较复杂
interface ExpressionStatement { type: 'ExpressionStatement'; expression: Expression; directive?: string; } // Expression 类型 type Expression = ThisExpression | Identifier | Literal | ArrayExpression | ObjectExpression | FunctionExpression | ArrowFunctionExpression | ClassExpression | TaggedTemplateExpression | MemberExpression | Super | MetaProperty | NewExpression | CallExpression | UpdateExpression | AwaitExpression | UnaryExpression | BinaryExpression | LogicalExpression | ConditionalExpression | YieldExpression | AssignmentExpression | SequenceExpression;小结
Esprima 本质上将 js 代码解析成了两大部分:
- 3 种变量声明(函数、变量和类)
- 表达式
其中表达式又被分为了两大类:
- 关键字组成的 statement,如 IfStatement, ForStatement等,这里面的BlockStatement有些特殊,因为其body又是 StatementListItem,产生递归。
- 运算语句(赋值、计算之类的操作)组成的 ExpressionStatement
看个例子:
// 解析 var answer = 6 * 7; if(true){answer =1} // 结果 { "type": "Program", "sourceType": "script", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "answer" }, "init": { "type": "BinaryExpression", "operator": "*", "left": { "type": "Literal", "value": 6, "raw": "6" }, "right": { "type": "Literal", "value": 7, "raw": "7" } } } ], "kind": "var" }, { "type": "IfStatement", "test": { "type": "Literal", "value": true, "raw": "true" }, "consequent": { "type": "BlockStatement", "body": [ { "type": "ExpressionStatement", "expression": { "type": "AssignmentExpression", "operator": "=", "left": { "type": "Identifier", "name": "answer" }, "right": { "type": "Literal", "value": 1, "raw": "1" } } } ] }, "alternate": null } ] }应用案例
去除 console.log() 语句,主要利用了 delegate 的第二个参数获取 console.log() 语句的位置,然后做字符串拼接
const esprima = require('esprima'); // console.log(x) or console['error'](y) function isConsoleCall(node) { return (node.type === 'CallExpression') && (node.callee.type === 'MemberExpression') && (node.callee.object.type === 'Identifier') && (node.callee.object.name === 'console'); } function removeCalls(source) { const entries = []; esprima.parseScript(source, {}, function (node, meta) { if (isConsoleCall(node)) { entries.push({ start: meta.start.offset, end: meta.end.offset }); } }); entries.sort((a, b) => { return b.end - a.end }).forEach(n => { source = source.slice(0, n.start) + source.slice(n.end); }); return source; }语法结构说明 Identifier
标识符,我觉得应该是这么叫的,就是我们写 JS 时自定义的名称,如变量名,函数名,属性名,都归为标识符。相应的接口是这样的:
interface Identifier <: Expression, Pattern { type: "Identifier"; name: string; }
一个标识符可能是一个表达式,或者是解构的模式(ES6 中的解构语法)。我们等会会看到 Expression和 Pattern 相关的内容的。
Literal字面量,这里不是指 [] 或者 {} 这些,而是本身语义就代表了一个值的字面量,如 1,“hello”, true 这些,还有正则表达式(有一个扩展的 Node 来表示正则表达式),如 /d?/。我们看一下文档的定义:
interface Literal <: Expression { type: "Literal"; value: string | boolean | null | number | RegExp; }
value 这里即对应了字面量的值,我们可以看出字面量值的类型,字符串,布尔,数值,null 和正则。
RegExpLiteral
这个针对正则字面量的,为了更好地来解析正则表达式的内容,添加多一个 regex 字段,里边会包括正则本身,以及正则的 flags。
interface RegExpLiteral <: Literal { regex: { pattern: string; flags: string; }; }Programs
一般这个是作为根节点的,即代表了一棵完整的程序代码树。
interface Program <: Node { type: "Program"; body: [ Statement ]; }
body 属性是一个数组,包含了多个 Statement(即语句)节点。
Functions函数声明或者函数表达式节点。
interface Function <: Node { id: Identifier | null; params: [ Pattern ]; body: BlockStatement; }
id 是函数名,params 属性是一个数组,表示函数的参数。body 是一个块语句。
有一个值得留意的点是,你在测试过程中,是不会找到 type: "Function" 的节点的,但是你可以找到 type: "FunctionDeclaration" 和 type: "FunctionExpression",因为函数要么以声明语句出现,要么以函数表达式出现,都是节点类型的组合类型,后边会再提及 FunctionDeclaration 和 FunctionExpression的相关内容。
这让人感觉这个文档规划得蛮细致的,函数名,参数和函数块是属于函数部分的内容,而声明或者表达式则有它自己需要的东西。
Statement语句节点没什么特别的,它只是一个节点,一种区分,但是语句有很多种,下边会详述。
interface Statement <: Node { }
ExpressionStatement
表达式语句节点,a = a + 1 或者 a++ 里边会有一个 expression 属性指向一个表达式节点对象(后边会提及表达式)。
interface ExpressionStatement <: Statement { type: "ExpressionStatement"; expression: Expression; }
BlockStatement
块语句节点,举个例子:if (...) { // 这里是块语句的内容 },块里边可以包含多个其他的语句,所以有一个 body 属性,是一个数组,表示了块里边的多个语句。
interface BlockStatement <: Statement { type: "BlockStatement"; body: [ Statement ]; }
EmptyStatement
一个空的语句节点,没有执行任何有用的代码,例如一个单独的分号 ;
interface EmptyStatement <: Statement { type: "EmptyStatement"; }
DebuggerStatement
debugger,就是表示这个,没有其他了。
interface DebuggerStatement <: Statement { type: "DebuggerStatement"; }
WithStatement
with 语句节点,里边有两个特别的属性,object 表示 with 要使用的那个对象(可以是一个表达式),body 则是对应 with 后边要执行的语句,一般会是一个块语句。
1
2
3
4
5
interface WithStatement <: Statement {
type: "WithStatement";
object: Expression;
body: Statement;
}
下边是控制流的语句:ReturnStatement
返回语句节点,argument 属性是一个表达式,代表返回的内容。
1
2
3
4
interface ReturnStatement <: Statement {
type: "ReturnStatement";
argument: Expression | null;
}
LabeledStatement
label 语句,平时可能会比较少接触到,举个例子:
1
2
3
4
5
6
7
loop: for(let i = 0; i < len; i++) {
// ...
for (let j = 0; j < min; j++) {
// ...
break loop;
}
}
这里的 loop 就是一个 label 了,我们可以在循环嵌套中使用 break loop 来指定跳出哪个循环。所以这里的 label 语句指的就是 loop: ... 这个。
一个 label 语句节点会有两个属性,一个 label 属性表示 label 的名称,另外一个 body 属性指向对应的语句,通常是一个循环语句或者 switch 语句。
1
2
3
4
5
interface LabeledStatement <: Statement {
type: "LabeledStatement";
label: Identifier;
body: Statement;
}
BreakStatement
break 语句节点,会有一个 label 属性表示需要的 label 名称,当不需要 label 的时候(通常都不需要),便是 null。
1
2
3
4
interface BreakStatement <: Statement {
type: "BreakStatement";
label: Identifier | null;
}
ContinueStatement
continue 语句节点,和 break 类似。
1
2
3
4
interface ContinueStatement <: Statement {
type: "ContinueStatement";
label: Identifier | null;
}
下边是条件语句:IfStatement
if 语句节点,很常见,会带有三个属性,test 属性表示 if (...) 括号中的表达式。
consequent 属性是表示条件为 true 时的执行语句,通常会是一个块语句。
alternate(候补,交替)属性则是用来表示 else 后跟随的语句节点,通常也会是块语句,但也可以又是一个 if 语句节点,即类似这样的结构: if (a) { //... } else if (b) { // ... }。 alternate 当然也可以为 null。
1
2
3
4
5
6
interface IfStatement <: Statement {
type: "IfStatement";
test: Expression;
consequent: Statement;
alternate: Statement | null;
}
SwitchStatement
switch 语句节点,有两个属性,discriminant 属性表示 switch 语句后紧随的表达式,通常会是一个变量,cases 属性是一个 case 节点的数组,用来表示各个 case 语句。
1
2
3
4
5
interface SwitchStatement <: Statement {
type: "SwitchStatement";
discriminant: Expression;
cases: [ SwitchCase ];
}
SwitchCase
switch 的 case 节点。test 属性代表这个 case 的判断表达式,consequent 则是这个 case 的执行语句。
当 test 属性是 null 时,则是表示 default 这个 case 节点。
1
2
3
4
5
interface SwitchCase <: Node {
type: "SwitchCase";
test: Expression | null;
consequent: [ Statement ];
}
下边是异常相关的语句:
ThrowStatement
throw 语句节点,argument 属性用以表示 throw 后边紧跟的表达式。
1
2
3
4
interface ThrowStatement <: Statement {
type: "ThrowStatement";
argument: Expression;
}
TryStatement
try 语句节点,block 属性表示 try 的执行语句,通常是一个块语句。
hanlder 属性是指 catch 节点,finalizer 是指 finally 语句节点,当 hanlder 为 null 时,finalizer 必须是一个块语句节点。
1
2
3
4
5
6
interface TryStatement <: Statement {
type: "TryStatement";
block: BlockStatement;
handler: CatchClause | null;
finalizer: BlockStatement | null;
}
CatchClause
catch 节点,param 用以表示 catch 后的参数,body 则表示 catch 后的执行语句,通常是一个块语句。
1
2
3
4
5
interface CatchClause <: Node {
type: "CatchClause";
param: Pattern;
body: BlockStatement;
}
下边是循环语句:WhileStatement
while 语句节点,test 表示括号中的表达式,body 是表示要循环执行的语句。
1
2
3
4
5
interface WhileStatement <: Statement {
type: "WhileStatement";
test: Expression;
body: Statement;
}
DoWhileStatement
do/while 语句节点,和 while 语句类似。
1
2
3
4
5
interface DoWhileStatement <: Statement {
type: "DoWhileStatement";
body: Statement;
test: Expression;
}
ForStatement
for 循环语句节点,属性 init/test/update 分别表示了 for 语句括号中的三个表达式,初始化值,循环判断条件,每次循环执行的变量更新语句(init 可以是变量声明或者表达式)。这三个属性都可以为 null,即 for(;;){}。 body 属性用以表示要循环执行的语句。
1
2
3
4
5
6
7
interface ForStatement <: Statement {
type: "ForStatement";
init: VariableDeclaration | Expression | null;
test: Expression | null;
update: Expression | null;
body: Statement;
}
ForInStatement
for/in 语句节点,left 和 right 属性分别表示在 in 关键词左右的语句(左侧可以是一个变量声明或者表达式)。body 依旧是表示要循环执行的语句。
1
2
3
4
5
6
interface ForInStatement <: Statement {
type: "ForInStatement";
left: VariableDeclaration | Pattern;
right: Expression;
body: Statement;
}
Declarations声明语句节点,同样也是语句,只是一个类型的细化。下边会介绍各种声明语句类型。
1
interface Declaration <: Statement { }
FunctionDeclaration
函数声明,和之前提到的 Function 不同的是,id 不能为 null。
1
2
3
4
interface FunctionDeclaration <: Function, Declaration {
type: "FunctionDeclaration";
id: Identifier;
}
VariableDeclaration
变量声明,kind 属性表示是什么类型的声明,因为 ES6 引入了 const/let。 declarations 表示声明的多个描述,因为我们可以这样:let a = 1, b = 2;。
1
2
3
4
5
interface VariableDeclaration <: Declaration {
type: "VariableDeclaration";
declarations: [ VariableDeclarator ];
kind: "var";
}
VariableDeclarator
变量声明的描述,id 表示变量名称节点,init 表示初始值的表达式,可以为 null。
1
2
3
4
5
interface VariableDeclarator <: Node {
type: "VariableDeclarator";
id: Pattern;
init: Expression | null;
}
Expressions表达式节点。
1
interface Expression <: Node { }
ThisExpression
表示 this。
1
2
3
interface ThisExpression <: Expression {
type: "ThisExpression";
}
ArrayExpression
数组表达式节点,elements 属性是一个数组,表示数组的多个元素,每一个元素都是一个表达式节点。
1
2
3
4
interface ArrayExpression <: Expression {
type: "ArrayExpression";
elements: [ Expression | null ];
}
ObjectExpression
对象表达式节点,property 属性是一个数组,表示对象的每一个键值对,每一个元素都是一个属性节点。
1
2
3
4
interface ObjectExpression <: Expression {
type: "ObjectExpression";
properties: [ Property ];
}
Property
对象表达式中的属性节点。key 表示键,value 表示值,由于 ES5 语法中有 get/set 的存在,所以有一个 kind 属性,用来表示是普通的初始化,或者是 get/set。
1
2
3
4
5
6
interface Property <: Node {
type: "Property";
key: Literal | Identifier;
value: Expression;
kind: "init" | "get" | "set";
}
FunctionExpression
函数表达式节点。
1
2
3
interface FunctionExpression <: Function, Expression {
type: "FunctionExpression";
}
下边是一元运算符相关的表达式部分:UnaryExpression
一元运算表达式节点(++/-- 是 update 运算符,不在这个范畴内),operator 表示运算符,prefix 表示是否为前缀运算符。argument 是要执行运算的表达式。
1
2
3
4
5
6
interface UnaryExpression <: Expression {
type: "UnaryExpression";
operator: UnaryOperator;
prefix: boolean;
argument: Expression;
}
UnaryOperator
一元运算符,枚举类型,所有值如下:
1
2
3
enum UnaryOperator {
"-" | "+" | "!" | "~" | "typeof" | "void" | "delete"
}
UpdateExpression
update 运算表达式节点,即 ++/--,和一元运算符类似,只是 operator 指向的节点对象类型不同,这里是 update 运算符。
1
2
3
4
5
6
interface UpdateExpression <: Expression {
type: "UpdateExpression";
operator: UpdateOperator;
argument: Expression;
prefix: boolean;
}
UpdateOperator
update 运算符,值为 ++ 或 --,配合 update 表达式节点的 prefix 属性来表示前后。
1
2
3
enum UpdateOperator {
"++" | "--"
}
下边是二元运算符相关的表达式部分:BinaryExpression
二元运算表达式节点,left 和 right 表示运算符左右的两个表达式,operator 表示一个二元运算符。
1
2
3
4
5
6
interface BinaryExpression <: Expression {
type: "BinaryExpression";
operator: BinaryOperator;
left: Expression;
right: Expression;
}
BinaryOperator
二元运算符,所有值如下:
1
2
3
4
5
6
7
8
enum BinaryOperator {
"==" | "!=" | "===" | "!=="
| "<" | "<=" | ">" | ">="
| "<<" | ">>" | ">>>"
| "+" | "-" | "*" | "/" | "%"
| "|" | "^" | "&" | "in"
| "instanceof"
}
AssignmentExpression
赋值表达式节点,operator 属性表示一个赋值运算符,left 和 right 是赋值运算符左右的表达式。
1
2
3
4
5
6
interface AssignmentExpression <: Expression {
type: "AssignmentExpression";
operator: AssignmentOperator;
left: Pattern | Expression;
right: Expression;
}
AssignmentOperator
赋值运算符,所有值如下:(常用的并不多)
1
2
3
4
5
enum AssignmentOperator {
"=" | "+=" | "-=" | "*=" | "/=" | "%="
| "<<=" | ">>=" | ">>>="
| "|=" | "^=" | "&="
}
LogicalExpression
逻辑运算表达式节点,和赋值或者二元运算类型,只不过 operator 是逻辑运算符类型。
1
2
3
4
5
6
interface LogicalExpression <: Expression {
type: "LogicalExpression";
operator: LogicalOperator;
left: Expression;
right: Expression;
}
LogicalOperator
逻辑运算符,两种值,即与或。
1
2
3
enum LogicalOperator {
"||" | "&&"
}
MemberExpression
成员表达式节点,即表示引用对象成员的语句,object 是引用对象的表达式节点,property 是表示属性名称,computed 如果为 false,是表示 . 来引用成员,property 应该为一个 Identifier 节点,如果 computed 属性为 true,则是 [] 来进行引用,即 property 是一个 Expression 节点,名称是表达式的结果值。
1
2
3
4
5
6
interface MemberExpression <: Expression, Pattern {
type: "MemberExpression";
object: Expression;
property: Expression;
computed: boolean;
}
下边是其他的一些表达式:ConditionalExpression
条件表达式,通常我们称之为三元运算表达式,即 boolean ? true : false。属性参考条件语句。
1
2
3
4
5
6
interface ConditionalExpression <: Expression {
type: "ConditionalExpression";
test: Expression;
alternate: Expression;
consequent: Expression;
}
CallExpression
函数调用表达式,即表示了 func(1, 2) 这一类型的语句。callee 属性是一个表达式节点,表示函数,arguments 是一个数组,元素是表达式节点,表示函数参数列表。
1
2
3
4
5
interface CallExpression <: Expression {
type: "CallExpression";
callee: Expression;
arguments: [ Expression ];
}
NewExpression
new 表达式。
1
2
3
interface NewExpression <: CallExpression {
type: "NewExpression";
}
SequenceExpression
这个就是逗号运算符构建的表达式(不知道确切的名称),expressions 属性为一个数组,即表示构成整个表达式,被逗号分割的多个表达式。
1
2
3
4
interface SequenceExpression <: Expression {
type: "SequenceExpression";
expressions: [ Expression ];
}
Patterns模式,主要在 ES6 的解构赋值中有意义,在 ES5 中,可以理解为和 Identifier 差不多的东西。
1
interface Pattern <: Node { }
参考:
https://www.jianshu.com/p/47d9b2a365c5
Esprima语法树结构详解 - 我的空间