AST使用(一)

关于安装及环境配置可以看https://mp.csdn.net/mp_blog/creation/editor/131155968

下面所有案例的JS原代码如下:

const a = 3;
let string = "hello";
for (let i = 0;i < 3;i++){
    string += "world"
}
console.log("string",string)

1.js代码生成ast:     

终端命令   babel-node code/basic1.js 可执行此js文件,code/basic1.js是js文件的路径

import { parse } from "@babel/parser";
import fs from "fs";

const code = fs.readFileSync("code/code1.js","utf-8");
let ast = parse(code);
console.log(ast)

2.ast复原js代码: 

import { parse } from "@babel/parser";
import generate from "@babel/generator";
import fs from "fs";

const code = fs.readFileSync("code/code1.js","utf-8");
let ast = parse(code);

const{ code: output } = generate(ast);
console.log(output)

此外关于generator方法,完整是这样的

const output = generate(ast, { /* options */ }, code)

第二个参数还可以配置一些选项,如下图,第三个参数可以接收原代码进行参考

AST使用(一)_第1张图片

3.ast中节点的遍历和字面量的修改

  // ast  traverse遍历节点
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import fs from "fs";
const code = fs.readFileSync("code/code1.js","utf-8");
let ast = parse(code);
//traverse可以遍历节点
traverse(ast,{
// enter方法是每个节点被遍历时都会调用的方法,path是节点的相关信息,path属于NodePath类型,有node,partent等属性,node属性就是当前节点,partent就是父节点
    enter(path){
        let node = path.node
        if (node.type === "NumericLiteral" && node.value === 3){
            node.value = 5;
        }
        if (node.type === "StringLiteral" && node.value === "hello"){
            node.value = "hi";
        }
    }
});
const{ code: output } = generate(ast);
console.log(output)

输出结果:

const a = 5;
let string = "hi";
for (let i = 0; i < 5; i++) {
  string += "world";
}
console.log("string", string);

可以看出现在的js代码与之前的js代码相比较,a的值由3变成5,string的初始值由hello变成了hi

除了enter方法获取遍历的节点以外,还可以用类型来捕获节点被调用,如下:

  // ast遍历及修改 traverse遍历节点
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import fs from "fs";
const code = fs.readFileSync("code/code1.js","utf-8");
let ast = parse(code);

traverse(ast,{
// NumericLiteral方法是每个数字字面量节点被遍历时都会调用的方法,StingLiteral就是字符串字面量
    NumericLiteral(path){
        let node = path.node
        if (node.value === 3){
            node.value = 5;
        }
    },
    StringLiteral(path){
        let node = path.node
        if (node.value === "hello") {
            node.value = "hi";
        }
    },
    
});
const{ code: output } = generate(ast);
console.log(output)

4.ast中语句的删除

我们以删除代码中最后一行console.log("string",string)为例:对应的就是下面圈出来的,只要把它删除就可以

AST使用(一)_第2张图片

 删除的代码如下

import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import fs from "fs";
const code = fs.readFileSync("code/code1.js","utf-8");
let ast = parse(code);

traverse(ast,{
    //删除console.log
    ExpressionStatement(path) {
        // ?.是可选链操作符  使用.查找属性时,找不到会报错。  使用?.找不到会返回undefined
        if(path.node?.expression?.callee?.object?.name === "console"){
            path.remove()
        }
    },

});
const{ code: output } = generate(ast);
console.log(output)

删除节点的方法是:path.remove();   

5.ast中的语句的插入和修改

之前的代码是没有const b = a + 1;这句的,现在我想在const a = 3后插入const b = a + 1;这句实现下面的效果

const a = 3;
const b = a + 1;   //这是使用ast插入的代码
let string = "hello";
for (let i = 0;i < 3;i++){
    string += "world"
}
console.log("string",string)

首先,将想要的代码在AST explorer中打开,查看b变量是如何构造的:

AST使用(一)_第3张图片

 AST使用(一)_第4张图片

将其展开后可以查看细节,我们若想构造b变量,就需要阅读官方的API了。

@babel/types · Babel 中文文档 - 印记中文 

AST使用(一)_第5张图片

 AST使用(一)_第6张图片

结合babel/types 的官方API 及 我们在网站中AST得出的结果,可以写出如下代码来构建

let init = types.binaryExpression(
    "+",
    types.identifier("a"),
    types.numericLiteral(1)
);
let declarator = types.variableDeclarator(types.identifier("b"),init);
let declaration = types.variableDeclaration("const",[declarator])

完整的插入及替换代码:

import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import * as types from "@babel/types";
import fs from "fs";
const code = fs.readFileSync("code/code1.js","utf-8");
let ast = parse(code);

traverse(ast,{
    VariableDeclaration(path){
        console.log(11111)
        //通过下列判断找到const a = 3所在的节点
        if(path.node?.kind === "const" && path.node?.declarations[0]?.id?.name === "a" && path.node?.declarations[0]?.init?.value === 3){
            let init = types.binaryExpression(
            "+",
            types.identifier("a"),
            types.numericLiteral(1)
            );
            let declarator = types.variableDeclarator(types.identifier("b"),init);
            let declaration = types.variableDeclaration("const",[declarator]);
            path.insertAfter(declaration);  //使用insertAfter插入节点
            path.stop()  //找到了就不再找了,类似于循环中的break
            // path.replaceWith(declaration)//替换为新的节点
            // path.remove() // 删除当前节点
            // let copyNode = types.cloneNode(path.node);//复制当前节点
            // traverse(copyNode, {
            //     enter(path){
            //         console.log(333333);
            //     }
            // }, {}, path);// 对子树进行遍历和替换,不影响当前的path
        };
    },
});
const{ code: output } = generate(ast);
console.log(output)

你可能感兴趣的:(javascript,开发语言,网络爬虫)