文章目录
- 1. path常用属性总结
- 1.1 path.node
- 1.2 path.scope
- 1.3 path.parentPath
- 1.4 path.parent
- 1.5 path.container
- 1.6 path.type
- 1.7 path.key
- 2. path常用方法总结
- 2.1 path.toString
- 2.2 path.replaceWith
- 2.3 path.replaceWithMultiple
- 2.4 path.remove
- 2.5 path.insertBefore
- 2.6 path.insertAfter
- 2.7 path.traverse
这一块儿可参考的总结资料不多,参考着蔡老板的文章学习一下,做下记录总结
1. path常用属性总结
path相关的源代码在这个js文件中,大家可以直接照着源码学习:
\node_modules\@babel\traverse\lib\path
这里选出部分常用的属性供大家参考。
path的属性定义:
class NodePath {
constructor(hub, parent) {
this.contexts = [];
this.state = null;
this.opts = null;
this._traverseFlags = 0;
this.skipKeys = null;
this.parentPath = null;
this.container = null;
this.listKey = null;
this.key = null;
this.node = null;
this.type = null;
this.parent = parent;
this.hub = hub;
this.data = null;
this.context = null;
this.scope = null;
}
......
}
1.1 path.node
表示当前path下的node节点,通常写插件,函数体的第一行代码就是:
let {node,scope} = path;
1.2 path.scope
表示当前path下的作用域,这个也是写插件经常会用到的。
具体的可以参考蔡老板这篇文章 : Scope和Binding常用方法及属性总结
scope相关:
1.scope.block
表示当前作用域下的所有node,参考上面的 this.block = node;
2.scope.dump()
输出当前每个变量的作用域信息。调用后直接打印,不需要加打印函数
3.scope.crawl()
重构scope,在某种情况下会报错,不过还是建议在每一个插件的最后一行加上。
4.scope.rename(oldName, newName, block)
修改当前作用域下的的指定的变量名,oldname、newname表示替换前后的变量名,为字符串。注意,oldName需要有binding,否则无法重命名。
5.scope.traverse(node, opts, state)
遍历当前作用域下的某些(个)插件。和全局的traverse用法一样。
6.scope.getBinding(name)
获取某个变量的binding,可以理解为其生命周期。包含引用,修改之类的信息
Binding:
目前我看到的只有 变量定义 和 函数定义 拥有binding,其他的获取binding都是undefined。
let binding = scope.getBinding(name);
例如:
var a = 123; 这里的 a 就拥有 binding。
而 function test(a,b,c) {};
函数名test以及形参a,b,c均拥有 binding。
1.binding.path
用于定位初始拥有binding的path;
2.binding.constant
用于判断当前变量是否被更改,true表示未改变,false表示有更改变量值。
3.binding.referenced
用于判断当前变量是否被引用,true表示代码下面有引用该变量的地方,false表示没有地方引用该变量。注意,引用和改变是分开的。
4.binding.referencePaths
它是一个Array类型,包含所有引用的path,多用于替换。
5. binding.constantViolations
它是一个Array类型,包含所有改变的path,多用于判断。
例如我们可以利用Scope.getBinding()方法来获取Binding
对象, 判断其引用情况来对语法树进行修改,
小例子,需求:想对以下js代码进行修改,删除所有定义了, 却从未使用的变量
var a = 1;
var b = 2;
function squire(){
var c = 3;
var d = 4;
return a * d;
var e = 5;
}
var f = 6;
可以这样写插件:
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const jscode = `
var a = 1;
var b = 2;
function squire(){
var c = 3;
var d = 4;
return a * d;
var e = 5;
}
var f = 6;
`;
let ast = parser.parse(jscode);
const visitor = {
VariableDeclarator(path)
{
const func_name = path.node.id.name;
const binding = path.scope.getBinding(func_name);
// 如果变量没有被引用过,那么删除也没关系
// 此处不能用有无修改过进行判断,因为没有被修改过并不意味着没用
if(binding && !binding.referenced){
path.remove();
}
},
}
traverse(ast, visitor);
console.log(generator(ast)['code']);
运行出来的结果:
1.3 path.parentPath
用于获取当前path下的父path,多用于判断节点类型。
1.4 path.parent
用于获取当前path下的父node,多用于判断节点类型。其中:
path.parent == path.parentPath.node;//这两者是等价的
1.5 path.container
用于获取当前path下的所有兄弟节点(包括自身),container翻译过来是容器的意思,它是一个Array类型,可以写个简单的插件来看看效果:
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
let jscode = "var a = 1,b = 2;var c = 3;";
let ast = parser.parse(jscode);
const getcontainer =
{
VariableDeclarator(path)
{
console.log(path.toString())
console.log(path.container.length);
}
}
traverse(ast,getcontainer);
代码中a,b互为兄弟节点,c只有一个节点,所以结果显示2,2,1。
这里要注意,是获取所有的所有兄弟节点(包括自身),不是path类型。
1.6 path.type
用于获取当前path的节点类型,写个简单的插件来看看效果 :
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
let jscode = "var a = 1 + 3;";
let ast = parser.parse(jscode)
const getType =
{
"VariableDeclarator|BinaryExpression|Identifier"(path)
{
console.log(path.toString());
console.log(path.type);
}
}
traverse(ast,getType);
1.7 path.key
用于获取当前path的key值,key通常用于path.get函数,当然还有更多的用法等待大家去挖掘 ,写个简单的插件来看看效果 :
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
let jscode = "var a = 1 + 2;";
let ast = parser.parse(jscode)
const getPathKey =
{
"VariableDeclarator|BinaryExpression|Identifier"(path)
{
console.log('path:',path.toString());
console.log(path.key);
}
}
traverse(ast,getPathKey);
第一个遍历的 path 是 VariableDeclarator 类型,对应源代码 是 a = 1+2 ;它的父节点是 VariableDeclaration 类型,而它处在 declarations 的第0的位置,因此输出是 0;
第二个遍历的 path 是 Identifier类型,对应源代码 是 a ;它是 id 节点,因此它的输出是 id;
第三个遍历的 path 是 BinaryExpression 类型,对应源代码 是 1+2 ;它是 init 节点,因此它的输出是 init。
2. path常用方法总结
2.1 path.toString
用来获取当前遍历path的js源代码,调用方式:
let sourceCode = path.toString();
这个非常有用,经常用于定于插件遍历的path,以便分析问题。
如果想通过 path.node来获取源代码,可以使用 generator 函数来获取:
let sourceCode = generator(path.node).code;
2.2 path.replaceWith
(单)节点替换函数,调用方式:
path.replaceWith(newNode);
实参一般是 node 类型,即将当前遍历的path替换为 实参里的 新节点。
注意,它不能用于 Array 的替换,即实参不能是Array类型。
2.3 path.replaceWithMultiple
(多)节点替换函数,调用方式:
path.rreplaceWithMultiple(ArrayNode);
实参一般是 Array 类型,它只能用于 Array 的替换。
即所有需要替换的节点在一个Array里面,常见的替换如 Block 类型节点里的 body.body。
2.4 path.remove
节点的删除,调用方式:
path.remove();
直接调用即可将当前遍历的所有符合条件的路径全部删除,所以使用的时候需要注意。
2.5 path.insertBefore
在当前节点前面插入新的节点。调用方式:
path.insertBefore(newNode);
2.6 path.insertAfter
在当前节点后面插入新的节点。调用方式:
path.insertAfter(newNode);
2.7 path.traverse
在当前节点下遍历其他的节点,比如有如下for循环:
for(;;)
{
a = b;
b = c;
c =d;
}
想要遍历这个for循环下面的 赋值语句,可以借助该函数进行遍历:
const visitFor =
{
ForStatement(path)
{
......
path.traverse({
AssignmentExpression(_path)
{
......
},
}),
......
},
}
文章到此结束,感谢您的阅读,下篇文章见!
AST学习课程推荐:
蔡老板和风佬课程:AST入门实战+零基础JavaScript补环境课程
也可以看蔡老板的知识星球学习:AST入门与实战