该组件主要用来判断节点类型、生成新的节点等。判断节点类型的方法很简单,例如,t.isldentifier(path.node),它等同于 path.node.type===”Identifier”。还可以在判断类型的同时附加条件,示例如下:

    1. const fs = require('fs');
    2. const parser = require("@babel/parser");
    3. const traverse = require("@babel/traverse").default;
    4. const t = require("@babel/types");
    5. const generator = require("@babel/generator").default;
    6. const jscode = fs.readFileSync("./demo.js", {
    7. encoding: "utf-8"
    8. });
    9. let ast = parser.parse(jscode);
    10. traverse(ast, {
    11. enter(path) {
    12. if (
    13. path.node.type === "Identifier" &&
    14. path.node.name === "n"
    15. ){
    16. path.node.name = "x";
    17. }
    18. }
    19. });
    20. let code = generator(ast).code;
    21. fs.writeFile('./demoNew.js', code, (err) => {});

    上述代码用来把标识符改为x,这是官方手册中的案例,但在实际修改中还需要考虑标识符的作用域。在这个案例中,visitor没有做任何过滤,遍历到任何一个节点都调用enter函数,所以要判断类型为Identifier且name的值为n,才修改为x。这个案例可以等同地写为:

    1. const fs = require('fs');
    2. const parser = require("@babel/parser");
    3. const traverse = require("@babel/traverse").default;
    4. const t = require("@babel/types");
    5. const generator = require("@babel/generator").default;
    6. const jscode = fs.readFileSync("./demo.js", {
    7. encoding: "utf-8"
    8. });
    9. let ast = parser.parse(jscode);
    10. traverse(ast, {
    11. enter(path) {
    12. if (
    13. t.isIdentifier(path.node, {name: 'n'})
    14. ){
    15. path.node.name = "x";
    16. }
    17. }
    18. });
    19. let code = generator(ast).code;
    20. fs.writeFile('./demoNew.js', code, (err) => {});

    如果要判断其他类型,只需要更改i后面的类型。这些方法还可以归纳为:当节点不符合要求,会抛出异常而不是返回true或false:

    1. t.assertBinaryExpression(maybeBinaryExpressionNode);
    2. t.assertBinaryExpression(maybeBinaryExpressionNode,{operator:"*"});

    可以看出,types组件中用于判断节点类型的函数是可以自己实现的,且过程也较为容易。因此,types组件最主要的功能是可以方便地生成新的节点。接下来尝试用types组件来生成原始代码。注意,Babel中的API有很多,不可能全部记住API的用法,一定要学会查看代码提示。
    在原始代码中,最开始是一个变量声明语句,类型为VariableDeclaration。因此,可以用
    t.VariableDeclaration去生成它。在vscode中输入”t.variabledeclaration”,然后将鼠标指针悬停在VariableDeclaration上,就会出现代码提示。也可以按Crl键,同时单击VariableDeclaration,跳转到一个以ts为后缀的文件中有如下一段代码:
    image.png
    image.png
    这段代码最后一个冒号后表示这个函数的返回值类型。括号里面的冒号前,是VariableDeclaration节点的属性。括号里面的冒号后,表示该参数允许传的类型。Array表示这个参数是一个数组。因此,变量声明语句的生成代码可以写为:

    1. const fs = require('fs');
    2. const parser = require("@babel/parser");
    3. const traverse = require("@babel/traverse").default;
    4. const t = require("@babel/types");
    5. const generator = require("@babel/generator").default;
    6. const jscode = fs.readFileSync("./demo.js", {
    7. encoding: "utf-8"
    8. });
    9. let ast = parser.parse(jscode);
    10. let loaclAst = t.valueToNode([1, "2", false, null, undefined, /\w\s/g, {x: '1000', y: 2000}]);
    11. let code = generator(loaclAst).code;
    12. console.log(code);
    13. let code = generator(ast).code;
    14. fs.writeFile('./demoNew.js', code, (err) => {});

    要生成上述代码中的varDec,需要先生成一个VariableDeclarator节点,表示变量声明的具体的值,在ts文件中的定义如下:

    1. export function variableDeclarator(id:LVal, init?:Expression | null):VariableDeclarator;

    VariableDeclarator是该函数的返回值,id和init是VariableDeclarator节点的属性。init后面的问号,代表该参数可省略。根据8.l.1节的分析,这里的id是Identifier类型。生成Identifier的方法很简单,在ts文件中的定义如下:

    1. export function identifier(name:any) : Identifier;

    在原始代码中对obj进行了初始化。所以这里的init是需要传值的。那么,生成varDec的代码可以写为:

    1. let varDec = t.variableDeclarator(t.identifier('obj'), objExpr);

    接着要生成objExpr。这里需要一个ObjectExpression,在ts文件中的定义如下:

    1. export function objectExpression(properties:Array <ObjectMethod | ObjectProperty | SpreadElement>) : ObjectExpression;

    对象的属性可以有多个,所以需要数组。因此,生成objExpr的代码可以写为:

    1. let objExpr = t.objectExpression([objProp1,objProp2,objProp3]);

    在上述代码中,objPropl、objProp2和objProp3都没有进行赋值。这里,还有一个新类型ObjectProperty,在ts文件中的定义如下:

    1. 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 文件中的定义如下:

    1. export function stringLiteral(value:string) : StringLiteral;

    因此,生成obj三个属性的代码为:

    1. let objProp1 = t.objectProperty(t.identifier('name'), t.stringLiteral('javaScriptAST'));
    2. let objProp2 = t.objectProperty(t.identifier('add'), funcExpr2);
    3. let objProp3 = t.objectProperty(t.identifier('mul'), funcExpr3);

    上述代码中,funcExpra2 和 funcExpr3 还没有进行赋值。接着介绍 FunctionExpression 在ts文件中的定义,id表示函数名,params 表示参数列表,body用 BlockStatement 包裹所有语句,其余参数可选,代码如下:

    1. export function functionExpression (id:Identifier | null | undefined, params:Array<Identifier | Pattern | RestElement | TSParameterProperty>, body: BlockStatement, generator?: boolean, async?: boolean): FunctionExpression;
    2. export function blockStatement (body:Array<Statement>, directives?: Array<Directive>):BlockStatement;

    在原始代码中都是由匿名函数直接赋值给obj的属性。因此,这里id为null,params列表用t.identifier生成,BlockStatement节点用 t.blockStatement 生成,代码如下:

    1. let a = t.identifier('a')
    2. let b = t.identifier('b')
    3. let bloSta2 = t.blockStatement([retSta2])
    4. let bloSta3 = t.blockStatement([retSta3])
    5. let funcExpr2 = t.functionExpression(null, [a, b], bloSta2)
    6. let funcExpr3 = t.functionExpression(null, [a, b], bloSta3)

    上述代码中,retSta2和retSta3还需另外生成。特别说明的是,如果要生成一个空函数,即函数体为空,则 blockStatement 的参数给空数组,而不是null。原始代码中,两个函数内都含有返回语句、二项式和数值字面量。在AST中可以分别使用ReturnStatement、BinaryExpression 和 NumericLiteral 来表示。它们在ts文件中的定义为:

    1. export function returnStatement(argument?:Expression | null):ReturnStatement;
    2. export function binaryExpression(operator:"+"|"-"|"/"|"%"|"*"|"**"|"&"|"|"|">>"|">>>"|"<<"|"^"|"=="|"==="|"!="|"!=="|"in"|"instanceof"|">"|"<"|">="|"<=",left:Expression,right:Expression):BinaryExpression;
    3. export function numericLiteral(value:number):NumericLiteral;

    接下来,把代码中剩余的部分生成完毕:

    1. let a = t.identifier('a')
    2. let b = t.identifier('b')
    3. let binExpr2 = t.binaryExpression('+', a, b)
    4. let binExpr3 = t.binaryExpression('×', a, b)
    5. let retSta2 = t.returnStatement(t.binaryExpression('+', binExpr2, t.numericLiteral(1000)))
    6. let retSta3 = t.returnStatement(t.binaryExpression('+', binExpr3, t.numericLiteral(1000)))
    7. let bloSta2 = t.blockStatement([retSta2])
    8. let bloSta3 = t.blockStatement([retSta3])

    完整的代码,以及执行之后的结果如下所示:

    1. let a = t.identifier('a')
    2. let b = t.identifier('b')
    3. let binExpr2 = t.binaryExpression('+', a, b)
    4. let binExpr3 = t.binaryExpression('*', a, b)
    5. let retSta2 = t.returnStatement(t.binaryExpression('+', binExpr2, t.numericLiteral(1000)))
    6. let retSta3 = t.returnStatement(t.binaryExpression('+', binExpr3, t.numericLiteral(1000)))
    7. let bloSta2 = t.blockStatement([retSta2])
    8. let bloSta3 = t.blockStatement([retSta3])
    9. let funcExpr2 = t.functionExpression(null, [a, b], bloSta2)
    10. let funcExpr3 = t.functionExpression(null, [a, b], bloSta3)
    11. let objProp1 = t.objectProperty(t.identifier('name'), t.stringLiteral('javaScriptAST'))
    12. let objProp2 = t.objectProperty(t.identifier('add'), funcExpr2)
    13. let objProp3 = t.objectProperty(t.identifier('mul'), funcExpr3)
    14. let objExpr = t.objectExpression([objProp1, objProp2, objProp3])
    15. let varDec = t.variableDeclarator(t.identifier('obj'), objExpr)
    16. let loaclAst = t.variableDeclaration('let', [varDec])
    17. let code = generator(loaclAst).code
    18. console.log(code)
    19. /* 输出结果
    20. let obj = {
    21. name: "javaScriptAST",
    22. add: function (a, b) {
    23. return a + b + 1000;
    24. },
    25. mul: function (a, b) {
    26. return a * b + 1000;
    27. }
    28. }
    29. */

    在J代码处理转换过程中,生成的新节点一般会添加或替换到已有的节点中。
    上述案例中,用到了 StringLiteral 和 NumericLiteral,同时在 Babel 中还定义了一些其他的字面量。

    1. export function nullLiteral(): NullLiteral;
    2. export function booleanLiteral(value: boolean): BooleanLiteral;
    3. export function regExpLiteral(pattern: string, flags?: any): RegExpLiteral;

    因此,不同的字面量需要调用不同的方法生成。当生成比较多的字面量时,难度会不断上升。其实在Babel中还提供了valueToNode,如下所示:

    1. export function valueToNode(value: undefined): Identifier
    2. export function valueToNode(value: boolean): BooleanLiteral
    3. export function valueToNode(value: null): NullLiteral
    4. export function valueToNode(value: string): StringLiteral
    5. export function valueToNode(value: number): NumericLiteral | BinaryExpression | UnaryExpression
    6. export function valueToNode(value: RegExp): RegExpLiteral
    7. export function valueToNode(value: ReadonlyArray<undefined | boolean | null | string | number | RegExp | object>): ArrayExpression
    8. export function valueToNode(value: object): ObjectExpression
    9. export function valueToNode(value: undefined | boolean | null | string | number | RegExp | object): Expression

    由此可以看出,valueToNode可以很方便地生成各种类型。除了原始类型undefined、null、string、number和boolean,还可以是对象类型RegExp、ReadonlyArray和object,示例代码如下:

    1. let loaclAst = t.valueToNode([1, "2", false, null, undefined, /w\s/g, { x: '1000', y: 2000 }]);
    2. let code = generator(loaclAst).code;
    3. console.log(code);

    结合ts文件中的定义和AST解析后的json数据,可以迅速掌握这些API的使用方法。
    前面的介绍中提到了Babel解析后的AST,其实它是一段json数据。因此,也可以按照AST的结构来构造一段json数据,以此生成想要的代码,但比使用types组件麻烦。

    1. let obj = {}
    2. obj.type = 'BinaryExpression';
    3. obj.left = { type: 'NumericLiteral', value: 1000 };
    4. obj.operator = '/'
    5. obj.right = { type: 'NumericLiteral', value: 2000 };
    6. let code = generator(obj).code;
    7. console.log(code);
    8. //输出 1000 / 2000