该组件主要用来判断节点类型、生成新的节点等。判断节点类型的方法很简单,例如,t.isldentifier(path.node),它等同于 path.node.type===”Identifier”。还可以在判断类型的同时附加条件,示例如下:
const fs = require('fs');
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
const jscode = fs.readFileSync("./demo.js", {
encoding: "utf-8"
});
let ast = parser.parse(jscode);
traverse(ast, {
enter(path) {
if (
path.node.type === "Identifier" &&
path.node.name === "n"
){
path.node.name = "x";
}
}
});
let code = generator(ast).code;
fs.writeFile('./demoNew.js', code, (err) => {});
上述代码用来把标识符改为x,这是官方手册中的案例,但在实际修改中还需要考虑标识符的作用域。在这个案例中,visitor没有做任何过滤,遍历到任何一个节点都调用enter函数,所以要判断类型为Identifier且name的值为n,才修改为x。这个案例可以等同地写为:
const fs = require('fs');
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
const jscode = fs.readFileSync("./demo.js", {
encoding: "utf-8"
});
let ast = parser.parse(jscode);
traverse(ast, {
enter(path) {
if (
t.isIdentifier(path.node, {name: 'n'})
){
path.node.name = "x";
}
}
});
let code = generator(ast).code;
fs.writeFile('./demoNew.js', code, (err) => {});
如果要判断其他类型,只需要更改i后面的类型。这些方法还可以归纳为:当节点不符合要求,会抛出异常而不是返回true或false:
t.assertBinaryExpression(maybeBinaryExpressionNode);
t.assertBinaryExpression(maybeBinaryExpressionNode,{operator:"*"});
可以看出,types组件中用于判断节点类型的函数是可以自己实现的,且过程也较为容易。因此,types组件最主要的功能是可以方便地生成新的节点。接下来尝试用types组件来生成原始代码。注意,Babel中的API有很多,不可能全部记住API的用法,一定要学会查看代码提示。
在原始代码中,最开始是一个变量声明语句,类型为VariableDeclaration。因此,可以用
t.VariableDeclaration去生成它。在vscode中输入”t.variabledeclaration”,然后将鼠标指针悬停在VariableDeclaration上,就会出现代码提示。也可以按Crl键,同时单击VariableDeclaration,跳转到一个以ts为后缀的文件中有如下一段代码:
这段代码最后一个冒号后表示这个函数的返回值类型。括号里面的冒号前,是VariableDeclaration节点的属性。括号里面的冒号后,表示该参数允许传的类型。Array表示这个参数是一个数组。因此,变量声明语句的生成代码可以写为:
const fs = require('fs');
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
const jscode = fs.readFileSync("./demo.js", {
encoding: "utf-8"
});
let ast = parser.parse(jscode);
let loaclAst = t.valueToNode([1, "2", false, null, undefined, /\w\s/g, {x: '1000', y: 2000}]);
let code = generator(loaclAst).code;
console.log(code);
let code = generator(ast).code;
fs.writeFile('./demoNew.js', code, (err) => {});
要生成上述代码中的varDec,需要先生成一个VariableDeclarator节点,表示变量声明的具体的值,在ts文件中的定义如下:
export function variableDeclarator(id:LVal, init?:Expression | null):VariableDeclarator;
VariableDeclarator是该函数的返回值,id和init是VariableDeclarator节点的属性。init后面的问号,代表该参数可省略。根据8.l.1节的分析,这里的id是Identifier类型。生成Identifier的方法很简单,在ts文件中的定义如下:
export function identifier(name:any) : Identifier;
在原始代码中对obj进行了初始化。所以这里的init是需要传值的。那么,生成varDec的代码可以写为:
let varDec = t.variableDeclarator(t.identifier('obj'), objExpr);
接着要生成objExpr。这里需要一个ObjectExpression,在ts文件中的定义如下:
export function objectExpression(properties:Array <ObjectMethod | ObjectProperty | SpreadElement>) : ObjectExpression;
对象的属性可以有多个,所以需要数组。因此,生成objExpr的代码可以写为:
let objExpr = t.objectExpression([objProp1,objProp2,objProp3]);
在上述代码中,objPropl、objProp2和objProp3都没有进行赋值。这里,还有一个新类型ObjectProperty,在ts文件中的定义如下:
export function objectProperty(key:any,value:Expression | PatternLike,computed?:boolean, shorthand?:any, decorators?:Array<Decorator> | null) : ObjectProperty;
key的值在原始代码中为name,它是一个Identifier。后面三个参数都是可选的,这里都选择不传入。其中,节点属性 computed 将在后续内容中介绍。value表示对象属性的具体的值。在原始代码中,第1个属性的值是一个字符串字面量,用 StringLiteral 表示。第2个和第3个属性的值都为函数表达式,用FunctionExpression表示。StringLiterala 在 ts 文件中的定义如下:
export function stringLiteral(value:string) : StringLiteral;
因此,生成obj三个属性的代码为:
let objProp1 = t.objectProperty(t.identifier('name'), t.stringLiteral('javaScriptAST'));
let objProp2 = t.objectProperty(t.identifier('add'), funcExpr2);
let objProp3 = t.objectProperty(t.identifier('mul'), funcExpr3);
上述代码中,funcExpra2 和 funcExpr3 还没有进行赋值。接着介绍 FunctionExpression 在ts文件中的定义,id表示函数名,params 表示参数列表,body用 BlockStatement 包裹所有语句,其余参数可选,代码如下:
export function functionExpression (id:Identifier | null | undefined, params:Array<Identifier | Pattern | RestElement | TSParameterProperty>, body: BlockStatement, generator?: boolean, async?: boolean): FunctionExpression;
export function blockStatement (body:Array<Statement>, directives?: Array<Directive>):BlockStatement;
在原始代码中都是由匿名函数直接赋值给obj的属性。因此,这里id为null,params列表用t.identifier生成,BlockStatement节点用 t.blockStatement 生成,代码如下:
let a = t.identifier('a')
let b = t.identifier('b')
let bloSta2 = t.blockStatement([retSta2])
let bloSta3 = t.blockStatement([retSta3])
let funcExpr2 = t.functionExpression(null, [a, b], bloSta2)
let funcExpr3 = t.functionExpression(null, [a, b], bloSta3)
上述代码中,retSta2和retSta3还需另外生成。特别说明的是,如果要生成一个空函数,即函数体为空,则 blockStatement 的参数给空数组,而不是null。原始代码中,两个函数内都含有返回语句、二项式和数值字面量。在AST中可以分别使用ReturnStatement、BinaryExpression 和 NumericLiteral 来表示。它们在ts文件中的定义为:
export function returnStatement(argument?:Expression | null):ReturnStatement;
export function binaryExpression(operator:"+"|"-"|"/"|"%"|"*"|"**"|"&"|"|"|">>"|">>>"|"<<"|"^"|"=="|"==="|"!="|"!=="|"in"|"instanceof"|">"|"<"|">="|"<=",left:Expression,right:Expression):BinaryExpression;
export function numericLiteral(value:number):NumericLiteral;
接下来,把代码中剩余的部分生成完毕:
let a = t.identifier('a')
let b = t.identifier('b')
let binExpr2 = t.binaryExpression('+', a, b)
let binExpr3 = t.binaryExpression('×', a, b)
let retSta2 = t.returnStatement(t.binaryExpression('+', binExpr2, t.numericLiteral(1000)))
let retSta3 = t.returnStatement(t.binaryExpression('+', binExpr3, t.numericLiteral(1000)))
let bloSta2 = t.blockStatement([retSta2])
let bloSta3 = t.blockStatement([retSta3])
完整的代码,以及执行之后的结果如下所示:
let a = t.identifier('a')
let b = t.identifier('b')
let binExpr2 = t.binaryExpression('+', a, b)
let binExpr3 = t.binaryExpression('*', a, b)
let retSta2 = t.returnStatement(t.binaryExpression('+', binExpr2, t.numericLiteral(1000)))
let retSta3 = t.returnStatement(t.binaryExpression('+', binExpr3, t.numericLiteral(1000)))
let bloSta2 = t.blockStatement([retSta2])
let bloSta3 = t.blockStatement([retSta3])
let funcExpr2 = t.functionExpression(null, [a, b], bloSta2)
let funcExpr3 = t.functionExpression(null, [a, b], bloSta3)
let objProp1 = t.objectProperty(t.identifier('name'), t.stringLiteral('javaScriptAST'))
let objProp2 = t.objectProperty(t.identifier('add'), funcExpr2)
let objProp3 = t.objectProperty(t.identifier('mul'), funcExpr3)
let objExpr = t.objectExpression([objProp1, objProp2, objProp3])
let varDec = t.variableDeclarator(t.identifier('obj'), objExpr)
let loaclAst = t.variableDeclaration('let', [varDec])
let code = generator(loaclAst).code
console.log(code)
/* 输出结果
let obj = {
name: "javaScriptAST",
add: function (a, b) {
return a + b + 1000;
},
mul: function (a, b) {
return a * b + 1000;
}
}
*/
在J代码处理转换过程中,生成的新节点一般会添加或替换到已有的节点中。
上述案例中,用到了 StringLiteral 和 NumericLiteral,同时在 Babel 中还定义了一些其他的字面量。
export function nullLiteral(): NullLiteral;
export function booleanLiteral(value: boolean): BooleanLiteral;
export function regExpLiteral(pattern: string, flags?: any): RegExpLiteral;
因此,不同的字面量需要调用不同的方法生成。当生成比较多的字面量时,难度会不断上升。其实在Babel中还提供了valueToNode,如下所示:
export function valueToNode(value: undefined): Identifier
export function valueToNode(value: boolean): BooleanLiteral
export function valueToNode(value: null): NullLiteral
export function valueToNode(value: string): StringLiteral
export function valueToNode(value: number): NumericLiteral | BinaryExpression | UnaryExpression
export function valueToNode(value: RegExp): RegExpLiteral
export function valueToNode(value: ReadonlyArray<undefined | boolean | null | string | number | RegExp | object>): ArrayExpression
export function valueToNode(value: object): ObjectExpression
export function valueToNode(value: undefined | boolean | null | string | number | RegExp | object): Expression
由此可以看出,valueToNode可以很方便地生成各种类型。除了原始类型undefined、null、string、number和boolean,还可以是对象类型RegExp、ReadonlyArray和object,示例代码如下:
let loaclAst = t.valueToNode([1, "2", false, null, undefined, /w\s/g, { x: '1000', y: 2000 }]);
let code = generator(loaclAst).code;
console.log(code);
结合ts文件中的定义和AST解析后的json数据,可以迅速掌握这些API的使用方法。
前面的介绍中提到了Babel解析后的AST,其实它是一段json数据。因此,也可以按照AST的结构来构造一段json数据,以此生成想要的代码,但比使用types组件麻烦。
let obj = {}
obj.type = 'BinaryExpression';
obj.left = { type: 'NumericLiteral', value: 1000 };
obj.operator = '/'
obj.right = { type: 'NumericLiteral', value: 2000 };
let code = generator(obj).code;
console.log(code);
//输出 1000 / 2000