转换操作
访问
获取子节点的Path
为了得到一个AST节点的属性值,我们一般先访问到该节点,然后利用 path.node.property
方法即可。
// the BinaryExpression AST node has properties: `left`, `right`, `operator`
BinaryExpression(path) {
path.node.left;
path.node.right;
path.node.operator;
}
如果你想访问到该属性内部的path
,使用path对象的get
方法,传递该属性的字符串形式作为参数。
BinaryExpression(path) {
path.get('left');
}
Program(path) {
path.get('body.0');
}
检查节点的类型
如果你想检查节点的类型,最好的方式是:
BinaryExpression(path) {
if (t.isIdentifier(path.node.left)) {
// ...
}
}
你同样可以对节点的属性们做浅层检查:
BinaryExpression(path) {
if (t.isIdentifier(path.node.left, { name: "n" })) {
// ...
}
}
功能上等价于:
BinaryExpression(path) {
if (
path.node.left != null &&
path.node.left.type === "Identifier" &&
path.node.left.name === "n"
) {
// ...
}
}
检查路径(Path)类型
一个路径具有相同的方法检查节点的类型:
BinaryExpression(path) {
if (path.get('left').isIdentifier({ name: "n" })) {
// ...
}
}
就相当于:
BinaryExpression(path) {
if (t.isIdentifier(path.node.left, { name: "n" })) {
// ...
}
}
检查标识符(Identifier)是否被引用
Identifier(path) {
if (path.isReferencedIdentifier()) {
// ...
}
}
或者:
Identifier(path) {
if (t.isReferenced(path.node, path.parent)) {
// ...
}
}
找到特定的父路径
有时你需要从一个路径向上遍历语法树,直到满足相应的条件。
对于每一个父路径调用callback
并将其NodePath
当作参数,当callback
返回真值时,则将其NodePath
返回。.
path.findParent((path) => path.isObjectExpression());
如果也需要遍历当前节点:
path.find((path) => path.isObjectExpression());
查找最接近的父函数或程序:
path.getFunctionParent();
向上遍历语法树,直到找到在列表中的父节点路径
path.getStatementParent();
获取同级路径
如果一个路径是在一个 Function
/Program
中的列表里面,它就有同级节点。
- 使用
path.inList
来判断路径是否有同级节点, - 使用
path.getSibling(index)
来获得同级路径, - 使用
path.key
获取路径所在容器的索引, - 使用
path.container
获取路径的容器(包含所有同级节点的数组) - 使用
path.listKey
获取容器的key
这些API用于 babel-minify </>中使用的 transform-merge-sibling-variables </>插件.
var a = 1; // pathA, path.key = 0
var b = 2; // pathB, path.key = 1
var c = 3; // pathC, path.key = 2
export default function({ types: t }) {
return {
visitor: {
VariableDeclaration(path) {
// if the current path is pathA
path.inList // true
path.listKey // "body"
path.key // 0
path.getSibling(0) // pathA
path.getSibling(path.key + 1) // pathB
path.container // [pathA, pathB, pathC]
}
}
};
}
停止遍历
如果你的插件需要在某种情况下不运行,最简单的做法是尽早写回。
BinaryExpression(path) {
if (path.node.operator !== '**') return;
}
如果您在顶级路径中进行子遍历,则可以使用2个提供的API方法:
path.skip()
skips traversing the children of the current path. path.stop()
stops traversal entirely.
outerPath.traverse({
Function(innerPath) {
innerPath.skip(); // if checking the children is irrelevant
},
ReferencedIdentifier(innerPath, state) {
state.iife = true;
innerPath.stop(); // if you want to save some state and then stop traversal, or deopt
}
});
处理
替换一个节点
BinaryExpression(path) {
path.replaceWith(
t.binaryExpression("**", path.node.left, t.numberLiteral(2))
);
}
function square(n) {
- return n * n;
+ return n ** 2;
}
用多节点替换单节点
ReturnStatement(path) {
path.replaceWithMultiple([
t.expressionStatement(t.stringLiteral("Is this the real life?")),
t.expressionStatement(t.stringLiteral("Is this just fantasy?")),
t.expressionStatement(t.stringLiteral("(Enjoy singing the rest of the song in your head)")),
]);
}
function square(n) {
- return n * n;
+ "Is this the real life?";
+ "Is this just fantasy?";
+ "(Enjoy singing the rest of the song in your head)";
}
**注意:</>当用多个节点替换一个表达式时,它们必须是 声明。 这是因为Babel在更换节点时广泛使用启发式算法,这意味着您可以做一些非常疯狂的转换,否则将会非常冗长。
用字符串源码替换节点
FunctionDeclaration(path) {
path.replaceWithSourceString(`function add(a, b) {
return a + b;
}`);
}
- function square(n) {
- return n * n;
+ function add(a, b) {
+ return a + b;
}
**注意:</>不建议使用这个API,除非您正在处理动态的源码字符串,否则在访问者外部解析代码更有效率。
插入兄弟节点
FunctionDeclaration(path) {
path.insertBefore(t.expressionStatement(t.stringLiteral("Because I'm easy come, easy go.")));
path.insertAfter(t.expressionStatement(t.stringLiteral("A little high, little low.")));
}
+ "Because I'm easy come, easy go.";
function square(n) {
return n * n;
}
+ "A little high, little low.";
注意:</>这里同样应该使用声明或者一个声明数组。 这个使用了在用多个节点替换一个节点</>中提到的相同的启发式算法。.
插入到容器(container)中
如果您想要在AST节点属性中插入一个像
body </ 0>那样的数组。 它与 <code> insertBefore
/insertAfter
类似, 但您必须指定listKey
(通常是正文
).
ClassMethod(path) {
path.get('body').unshiftContainer('body', t.expressionStatement(t.stringLiteral('before')));
path.get('body').pushContainer('body', t.expressionStatement(t.stringLiteral('after')));
}
class A {
constructor() {
+ "before"
var a = 'middle';
+ "after"
}
}
删除一个节点
FunctionDeclaration(path) {
path.remove();
}
- function square(n) {
- return n * n;
- }
替换父节点
只需使用parentPath:` path.parentPath 调用 replaceWith
即可
BinaryExpression(path) {
path.parentPath.replaceWith(
t.expressionStatement(t.stringLiteral("Anyway the wind blows, doesn't really matter to me, to me."))
);
}
`
function square(n) {
- return n * n;
+ "Anyway the wind blows, doesn't really matter to me, to me.";
}
删除父节点
BinaryExpression(path) {
path.parentPath.remove();
}
function square(n) {
- return n * n;
}
Scope(作用域)
检查本地变量是否被绑定
FunctionDeclaration(path) {
if (path.scope.hasBinding("n")) {
// ...
}
}
这将遍历范围树并检查特定的绑定。
您也可以检查一个作用域是否有**自己的</>绑定:
FunctionDeclaration(path) {
if (path.scope.hasOwnBinding("n")) {
// ...
}
}
创建一个 UID
这将生成一个标识符,不会与任何本地定义的变量相冲突。
FunctionDeclaration(path) {
path.scope.generateUidIdentifier("uid");
// Node { type: "Identifier", name: "_uid" }
path.scope.generateUidIdentifier("uid");
// Node { type: "Identifier", name: "_uid2" }
}
提升变量声明至父级作用域
有时你可能想要推送一个` VariableDeclaration </>,这样你就可以分配给它。
FunctionDeclaration(path) {
const id = path.scope.generateUidIdentifierBasedOnNode(path.node.id);
path.remove();
path.scope.parent.push({ id, init: path.node });
}
`
- function square(n) {
+ var _square = function square(n) {
return n * n;
- }
+ };
重命名绑定及其引用
FunctionDeclaration(path) {
path.scope.rename("n", "x");
}
- function square(n) {
- return n * n;
+ function square(x) {
+ return x * x;
}
或者,您可以将绑定重命名为生成的唯一标识符:
FunctionDeclaration(path) {
path.scope.rename("n");
}
- function square(n) {
- return n * n;
+ function square(_n) {
+ return _n * _n;
}