1.AST API详解

1.AST入门

AST Explprer 在线

分析结构

2.Babel中的组件

API查询

babel api 查询

准备:

导入环境

// 导入包

/* 读写文件*/
const fs = require('fs');
/*js源码到ast的过程*/
const parser = require("@babel/parser");
/*便利ast节点*/
const traverse = require("@babel/traverse").default;
/*判断ast节点的类型,构建新的ast节点*/
const t = require("@babel/types");
/*ast到源码的过程*/
const generator = require("@babel/generator").default;
/*导入js文件*/
const jscode = fs.readFileSync("./demo.js",{
    encoding:"utf-8"
});
/*将js代码转换为ast*/
var ast = parser.parse(jscode);


处理ast的位置.........



/*将ast转为代码*/
var code = generator(ast).code;
/*将js代码保存到文件*/
fs.writeFile('./demoNew.js',code,(err)=>{});

a)parser与generator

如果代码中包含import或export 在用ast解析会报错

parser

var ast = parser.parse(jscode,{
    sourceType:"module";
})

generator

/*将ast转为代码*/
var code = generator(ast,{
    concise:false,
    comments:false,
    retainLines:false,
    minified:false,
    jsescOption:{
        minimal:true
    }
}).code;

b)traverse 与 visitor

traverse 接收两个参数

第一个参数:ast

第二个参数:visitor过滤器

/*将js代码转换为ast*/
var ast = parser.parse(jscode);
var visitor = {};
visitor.FunctionDeclaration = function(path){
    console.log("this is a FunctionDeclaration~")
};
/*visitor 是过滤器*/
traverse(ast,visitor);

visitor定义写法

//1
var visitor = {
    FunctionExpression :function(path){
        console.log("liuliuliu")    
    }
}
//2
var visitor = {};
visitor.FunctionDeclaration = function(path){
    console.log("this is a FunctionDeclaration~")
};
//3
const visitor = {
    FunctionDeclaration(path){
        console.log("123")    
    }
}
//4
const visitor = {
    FunctionDeclaration:{
        enter(path){
            console.log("enter")        
        },
        exit(path){
            console.log("exit")        
        }    
    }
}

可以用 | 分割 "FunctionExpression|BinaryExpression"形式的字符串,把同一个函数应用到多个节点

var visitor = {
    "FunctionExpression|ReturnStatement":{
        enter(path){
            console.log("enter~")
        },
        exit(path){
            console.log("exit")
        }
    }
}

把多个函数应用到同一个节点

function a(){console.log("a~")}
function b(){console.log("b~")}
var visitor = {
    "FunctionExpression|ReturnStatement":{
        enter:[a,b],
        exit:[a,b]
    }
}

path中的 traverse

var visitor_2 ={
    Identifier:function(path){
        path.node.name = this.value;
    }
}
var visitor = {
    "FunctionExpression":{
        enter(path){
            var params_list = path.node.params
            var operator = path.node.body.body[0].argument.operator
            console.log(operator)
            for(var i =0;i<params_list.length;i++){
                var Identifier_name = params_list[i].name
                console.log(Identifier_name)
            }
        },
        exit(path){
            /*
            在path下的traverse
            参数1: visitor 过滤器
            参数2: 一个对象
            */
            path.traverse(visitor_2,{
                value:"替换掉"
            })
        }
    }
}
/*visitor 是过滤器*/
traverse(ast,visitor);

types组件

types判断节点类型

var visitor = {
    ObjectProperty(path){
        /*判断节点*/
       if(t.isObjectProperty(path.node) && path.node.key.name == "add"){
           path.node.key.name ="hello"
       }
    }
}

types构造代码

var a = t.identifier('a');
var b = t.identifier('b');
var c = t.identifier('c');
var binExpr = t.binaryExpression("+",t.binaryExpression("+",a,b),c)
var retSta = t.returnStatement(t.binaryExpression("+",binExpr,t.numericLiteral(1000)))
var bloSta = t.blockStatement([retSta])
var funcExpr = t.functionExpression(null,[a,b,c],bloSta)
var objProp1 = t.objectProperty(t.identifier('name'),t.stringLiteral("liuhanwen"))
var objprop2 = t.objectProperty(t.identifier('add'),funcExpr)
var objExpr = t.objectExpression([objProp1,objprop2])
var varDec = t.variableDeclarator(t.identifier('obj'),objExpr)
var localAst = t.variableDeclaration('var',[varDec])
var code = generator(localAst).code
console.log(code)

3.Path对象详解

1.Path与Node的区别

Node是Path中的一部分

Path包含Node

2.Path中的方法

/*将name节点包装成path对象*/
var path = path.get(name)
/*path判断节点类型*/
var b = path.get(xxxx).isIdentifier()
/*path判断节点类型,不符合就报错*/
var b = path.get(xxx).assertIdentifier()

Path节点转代码

//1
var visitor={
    FunctionExpression(path){
        console.log(generator(path.node).code)
    }
}
//2
var visitor={
    FunctionExpression(path){
        console.log(path.toString())
    }
}
//3
var visitor={
    FunctionExpression(path){
        console.log(path+"")
    }
}

Path替换节点属性

path.node.argument = t.binaryExpression("+",t.stringLiteral("aaa"),t.stringLiteral("bbb"))

Path替换当前整个节点(一换一)

//将整个节点替换成
//valueToNode是将字面量转为Node
path.replaceWith(t.valueToNode("hello~"))

Path替换当前整个节点(一换多)

var visitor={
    StringLiteral(path){
        //替换单一节点
        path.replaceInline(t.stringLiteral("hello AST"));
        path.skip()
    },
    ReturnStatement(path){
        //传入列表替换多
        path.replaceInline([
            t.expressionStatement(t.stringLiteral("liuhanwen")),
            t.expressionStatement(t.numericLiteral(1000)),
            t.returnStatement(t.numericLiteral(1000))
        ])
        path.stop();
    }
}

用字符串替换节点内容

var visitor={
    ReturnStatement(path){
        var argument_path = path.get('argument');
        argument_path.replaceWithSourceString(
            'function(){return '+argument_path+'}()'
        );
        path.stop()
    }
}

删除节点

EmptyStatement(path){
    path.remove();
}

插入节点

ReturnStatement(path){
    path.insertBefore()
    path.insertAfter()
}

3.父级Path

1.parentPath 与parent的关系

path.parentPath.node 等价于 path.parent

parent是parentPath中的一部分

2.path.findParent()

向上遍历父节点~如果找到了就返回这个父级Path

var visitor={
    ReturnStatement(path){
        console.log(path.findParent(function(p){return p.isBlockStatement()}))
    }
}

3.path.find()

find和findParent唯一区别是,find查找范围包括当前节点 而 findParent不包含

4.path.getFunctionParent()

向上查找与当前节点最接近的父函数path.getFunctionParent()返回的也是path对象

找到当前节点最近的父函数

5.path.getStatementParent()

向上遍历语法树,直到找到语句父节点.比如.声明语句,return语句,if语句,witch语句,while语句等等

返回的也是path对象

该方法从当前节点开始找起,因此,如果想要找到return语句的父语句,就需要从parentPath中去调用

6.父级Path的其他方法

替换父节点path.parentPath.repleceWith(Node)

删除父节点.path.parentPath.remove()

4.同级Path

前置知识

容器(container)

container为对象的情况下是无意义的

container为数组的情况下

1.path.inList(判断容器是否为数组)

用于判断是否有同级节点.

当container为数组,但是只有一个成员时,也会返回true

2.path.conainer,path.listKey,path.key

path.key 获取当前节点在容器中的索引

path.container 获取容器(包含所有同级节点的数组).

path.listKey 获取容器名

3.path.getSibling(index)

用于获取同级Path,其中参数index即容器数组中的索引.index可以通过path.key来获取

可以对path.key进行加减操作,来定位到不同的同级path

4.unshiftContainer 与 pushContainer

unshiftContainer在容器前面加入

pushContainer在容器后面加入

4.scope详解

1.获取标识符作用域

获取当前标识符的作用域

path.scope.block

获取函数作用域

path.scope.parent.block  #获取父级作用域

2.获取标识符的绑定

常用

let binding = path.scope.getBinding('a')

获取当前节点自己的绑定

scope.getOwnBinding(name)

3.referencePaths与constantViolations

referencePaths

是被引用标识的数组

constantViolations

是被赋值标识的数组

4.遍历作用域

path.scope.traverse 遍历
binding.scope.traverse 遍历
traverse(ast,{
    FunctionDeclaraion(path){
        let binding = path.scope.getBinding('a');
        #遍历作用域的第一个参数是       作用域节点
        binding.scope.traverse(binding.scope.block,{
            AssignmentExpression(p){
                if(p.node.left.name == 'a')
                    p.node.right = t.numericLiteral(500);            
            }        
        })    
    }
})

5.标识符重命名

binding对象.scope.rename("x",'b') 所有引用的地方都会修改掉

生成标识符的方法

traverse(ast,{
    FunctionDeclaraion(path){
        #第一次uid
        path.scope.generateUidIdentifier("uid")   
        #第二次uid2
        path.scope.generateUidIdentifier("uid")
        #第三次uid3
        path.scope.generateUidIdentifier("uid") 
    }
})

6.scope的其他方法

scope.hasBinding('a') 判断是否绑定 
scope.hasOwnBinding('a')判断是否有自己的绑定,
scope.getAllBindings() 获取对象中的所有绑定 获取一个对象
scope.hasReference('a') 查询当前节点中是否有a标识符的引用,返回true或false
scope.getBindingIdentifier('a') 获取当前节点中绑定a标识,返回的是Identifier的Node对象
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值