转换操作

访问

获取子节点的Path

为了得到一个AST节点的属性值,我们一般先访问到该节点,然后利用 path.node.property 方法即可。

  1. // the BinaryExpression AST node has properties: `left`, `right`, `operator`
  2. BinaryExpression(path) {
  3. path.node.left;
  4. path.node.right;
  5. path.node.operator;
  6. }

如果你想访问到该属性内部的path,使用path对象的get方法,传递该属性的字符串形式作为参数。

  1. BinaryExpression(path) {
  2. path.get('left');
  3. }
  4. Program(path) {
  5. path.get('body.0');
  6. }

检查节点的类型

如果你想检查节点的类型,最好的方式是:

  1. BinaryExpression(path) {
  2. if (t.isIdentifier(path.node.left)) {
  3. // ...
  4. }
  5. }

你同样可以对节点的属性们做浅层检查:

  1. BinaryExpression(path) {
  2. if (t.isIdentifier(path.node.left, { name: "n" })) {
  3. // ...
  4. }
  5. }

功能上等价于:

  1. BinaryExpression(path) {
  2. if (
  3. path.node.left != null &&
  4. path.node.left.type === "Identifier" &&
  5. path.node.left.name === "n"
  6. ) {
  7. // ...
  8. }
  9. }

检查路径(Path)类型

一个路径具有相同的方法检查节点的类型:

  1. BinaryExpression(path) {
  2. if (path.get('left').isIdentifier({ name: "n" })) {
  3. // ...
  4. }
  5. }

就相当于:

  1. BinaryExpression(path) {
  2. if (t.isIdentifier(path.node.left, { name: "n" })) {
  3. // ...
  4. }
  5. }

检查标识符(Identifier)是否被引用

  1. Identifier(path) {
  2. if (path.isReferencedIdentifier()) {
  3. // ...
  4. }
  5. }

或者:

  1. Identifier(path) {
  2. if (t.isReferenced(path.node, path.parent)) {
  3. // ...
  4. }
  5. }

找到特定的父路径

有时你需要从一个路径向上遍历语法树,直到满足相应的条件。

对于每一个父路径调用callback并将其NodePath当作参数,当callback返回真值时,则将其NodePath返回。.

  1. path.findParent((path) => path.isObjectExpression());

如果也需要遍历当前节点:

  1. path.find((path) => path.isObjectExpression());

查找最接近的父函数或程序:

  1. path.getFunctionParent();

向上遍历语法树,直到找到在列表中的父节点路径

  1. path.getStatementParent();

获取同级路径

如果一个路径是在一个 FunctionProgram中的列表里面,它就有同级节点。

  • 使用path.inList来判断路径是否有同级节点,
  • 使用path.getSibling(index)来获得同级路径,
  • 使用 path.key获取路径所在容器的索引,
  • 使用 path.container获取路径的容器(包含所有同级节点的数组)
  • 使用 path.listKey获取容器的key

这些API用于 babel-minify </>中使用的 transform-merge-sibling-variables </>插件.

  1. var a = 1; // pathA, path.key = 0
  2. var b = 2; // pathB, path.key = 1
  3. var c = 3; // pathC, path.key = 2
  1. export default function({ types: t }) {
  2. return {
  3. visitor: {
  4. VariableDeclaration(path) {
  5. // if the current path is pathA
  6. path.inList // true
  7. path.listKey // "body"
  8. path.key // 0
  9. path.getSibling(0) // pathA
  10. path.getSibling(path.key + 1) // pathB
  11. path.container // [pathA, pathB, pathC]
  12. }
  13. }
  14. };
  15. }

停止遍历

如果你的插件需要在某种情况下不运行,最简单的做法是尽早写回。

  1. BinaryExpression(path) {
  2. if (path.node.operator !== '**') return;
  3. }

如果您在顶级路径中进行子遍历,则可以使用2个提供的API方法:

path.skip() skips traversing the children of the current path. path.stop() stops traversal entirely.

  1. outerPath.traverse({
  2. Function(innerPath) {
  3. innerPath.skip(); // if checking the children is irrelevant
  4. },
  5. ReferencedIdentifier(innerPath, state) {
  6. state.iife = true;
  7. innerPath.stop(); // if you want to save some state and then stop traversal, or deopt
  8. }
  9. });

处理

替换一个节点

  1. BinaryExpression(path) {
  2. path.replaceWith(
  3. t.binaryExpression("**", path.node.left, t.numberLiteral(2))
  4. );
  5. }
  1. function square(n) {
  2. - return n * n;
  3. + return n ** 2;
  4. }

用多节点替换单节点

  1. ReturnStatement(path) {
  2. path.replaceWithMultiple([
  3. t.expressionStatement(t.stringLiteral("Is this the real life?")),
  4. t.expressionStatement(t.stringLiteral("Is this just fantasy?")),
  5. t.expressionStatement(t.stringLiteral("(Enjoy singing the rest of the song in your head)")),
  6. ]);
  7. }
  1. function square(n) {
  2. - return n * n;
  3. + "Is this the real life?";
  4. + "Is this just fantasy?";
  5. + "(Enjoy singing the rest of the song in your head)";
  6. }

**注意:</>当用多个节点替换一个表达式时,它们必须是 声明。 这是因为Babel在更换节点时广泛使用启发式算法,这意味着您可以做一些非常疯狂的转换,否则将会非常冗长。

用字符串源码替换节点

  1. FunctionDeclaration(path) {
  2. path.replaceWithSourceString(`function add(a, b) {
  3. return a + b;
  4. }`);
  5. }
  1. - function square(n) {
  2. - return n * n;
  3. + function add(a, b) {
  4. + return a + b;
  5. }

**注意:</>不建议使用这个API,除非您正在处理动态的源码字符串,否则在访问者外部解析代码更有效率。

插入兄弟节点

  1. FunctionDeclaration(path) {
  2. path.insertBefore(t.expressionStatement(t.stringLiteral("Because I'm easy come, easy go.")));
  3. path.insertAfter(t.expressionStatement(t.stringLiteral("A little high, little low.")));
  4. }
  1. + "Because I'm easy come, easy go.";
  2. function square(n) {
  3. return n * n;
  4. }
  5. + "A little high, little low.";

注意:</>这里同样应该使用声明或者一个声明数组。 这个使用了在用多个节点替换一个节点</>中提到的相同的启发式算法。.

插入到容器(container)中

如果您想要在AST节点属性中插入一个像body </ 0>那样的数组。 它与 <code> insertBefore/insertAfter 类似, 但您必须指定 listKey (通常是 正文).

  1. ClassMethod(path) {
  2. path.get('body').unshiftContainer('body', t.expressionStatement(t.stringLiteral('before')));
  3. path.get('body').pushContainer('body', t.expressionStatement(t.stringLiteral('after')));
  4. }
  1. class A {
  2. constructor() {
  3. + "before"
  4. var a = 'middle';
  5. + "after"
  6. }
  7. }

删除一个节点

  1. FunctionDeclaration(path) {
  2. path.remove();
  3. }
  1. - function square(n) {
  2. - return n * n;
  3. - }

替换父节点

只需使用parentPath:` path.parentPath 调用 replaceWith 即可

  1. BinaryExpression(path) {
  2. path.parentPath.replaceWith(
  3. t.expressionStatement(t.stringLiteral("Anyway the wind blows, doesn't really matter to me, to me."))
  4. );
  5. }
  6. `
  1. function square(n) {
  2. - return n * n;
  3. + "Anyway the wind blows, doesn't really matter to me, to me.";
  4. }

删除父节点

  1. BinaryExpression(path) {
  2. path.parentPath.remove();
  3. }
  1. function square(n) {
  2. - return n * n;
  3. }

Scope(作用域)

检查本地变量是否被绑定

  1. FunctionDeclaration(path) {
  2. if (path.scope.hasBinding("n")) {
  3. // ...
  4. }
  5. }

这将遍历范围树并检查特定的绑定。

您也可以检查一个作用域是否有**自己的</>绑定:

  1. FunctionDeclaration(path) {
  2. if (path.scope.hasOwnBinding("n")) {
  3. // ...
  4. }
  5. }

创建一个 UID

这将生成一个标识符,不会与任何本地定义的变量相冲突。

  1. FunctionDeclaration(path) {
  2. path.scope.generateUidIdentifier("uid");
  3. // Node { type: "Identifier", name: "_uid" }
  4. path.scope.generateUidIdentifier("uid");
  5. // Node { type: "Identifier", name: "_uid2" }
  6. }

提升变量声明至父级作用域

有时你可能想要推送一个` VariableDeclaration </>,这样你就可以分配给它。

  1. FunctionDeclaration(path) {
  2. const id = path.scope.generateUidIdentifierBasedOnNode(path.node.id);
  3. path.remove();
  4. path.scope.parent.push({ id, init: path.node });
  5. }
  6. `
  1. - function square(n) {
  2. + var _square = function square(n) {
  3. return n * n;
  4. - }
  5. + };

重命名绑定及其引用

  1. FunctionDeclaration(path) {
  2. path.scope.rename("n", "x");
  3. }
  1. - function square(n) {
  2. - return n * n;
  3. + function square(x) {
  4. + return x * x;
  5. }

或者,您可以将绑定重命名为生成的唯一标识符:

  1. FunctionDeclaration(path) {
  2. path.scope.rename("n");
  3. }
  1. - function square(n) {
  2. - return n * n;
  3. + function square(_n) {
  4. + return _n * _n;
  5. }