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对象