• Transformation Operations" level="1">Transformation Operations
    • Visiting" level="2">Visiting
      • Get the Path of Sub-Node" level="3">Get the Path of Sub-Node
      • Check if a node is a certain type" level="3">Check if a node is a certain type
      • Check if a path is a certain type" level="3">Check if a path is a certain type
      • Check if an identifier is referenced" level="3">Check if an identifier is referenced
      • Find a specific parent path" level="3">Find a specific parent path
      • Get Sibling Paths" level="3">Get Sibling Paths
      • Stopping Traversal" level="3">Stopping Traversal
    • Manipulation" level="2">Manipulation
      • Replacing a node" level="3">Replacing a node
      • Replacing a node with multiple nodes" level="3">Replacing a node with multiple nodes
      • Replacing a node with a source string" level="3">Replacing a node with a source string
      • Inserting a sibling node" level="3">Inserting a sibling node
      • Inserting into a container" level="3">Inserting into a container
      • Removing a node" level="3">Removing a node
      • Replacing a parent" level="3">Replacing a parent
      • Removing a parent" level="3">Removing a parent
    • Scope" level="2">Scope
      • Checking if a local variable is bound" level="3">Checking if a local variable is bound
      • Generating a UID" level="3">Generating a UID
      • Pushing a variable declaration to a parent scope" level="3">Pushing a variable declaration to a parent scope
      • Rename a binding and its references" level="3">Rename a binding and its references

    Transformation Operations" class="reference-link">Transformation Operations

    Visiting" class="reference-link">Visiting

    Get the Path of Sub-Node" class="reference-link">Get the Path of Sub-Node

    To access an AST node’s property you normally access the node and then the property. 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. }

    If you need to access the path of that property instead, use the get method of a path, passing in the string to the property.

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

    You can’t current use get on a Container (the body array of a BlockStatement), but you chain the dot syntax instead.

    1. export default function f() {
    2. return bar;
    3. }

    For the example above, if you wanted to get the path corresponding to the return, you could chain the various properties, using a number as the index when traversing the array.

    1. ExportDefaultDeclaration(path) {
    2. path.get("declaration.body.body.0");
    3. }

    Check if a node is a certain type" class="reference-link">Check if a node is a certain type

    If you want to check what the type of a node is, the preferred way to do so is:

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

    You can also do a shallow check for properties on that node:

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

    This is functionally equivalent to:

    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. }

    Check if a path is a certain type" class="reference-link">Check if a path is a certain type

    A path has the same methods for checking the type of a node:

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

    is equivalent to doing:

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

    Check if an identifier is referenced" class="reference-link">Check if an identifier is referenced

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

    Alternatively:

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

    Find a specific parent path" class="reference-link">Find a specific parent path

    Sometimes you will need to traverse the tree upwards from a path until a condition is satisfied.

    Call the provided callback with the NodePaths of all the parents. When the callback returns a truthy value, we return that NodePath.

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

    If the current path should be included as well:

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

    Find the closest parent function or program:

    1. path.getFunctionParent();

    Walk up the tree until we hit a parent node path in a list

    1. path.getStatementParent();

    Get Sibling Paths" class="reference-link">Get Sibling Paths

    If a path is in a list like in the body of a Function/Program, it will have “siblings”.

    • Check if a path is part of a list with path.inList
    • You can get the surrounding siblings with path.getSibling(index),
    • The current path’s index in the container with path.key,
    • The path’s container (an array of all sibling nodes) with path.container
    • Get the name of the key of the list container with path.listKey

    These APIs are used in the transform-merge-sibling-variables plugin used in babel-minify.

    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. path.getPrevSibling() // path(undefined) *
    13. path.getNextSibling() // pathB
    14. path.getAllPrevSiblings() // []
    15. path.getAllNextSiblings() // [pathB, pathC]
    16. }
    17. }
    18. };
    19. }
    • path(undefined) is a NodePath where the path.node === undefined

    Stopping Traversal" class="reference-link">Stopping Traversal

    If your plugin needs to not run in a certain situation, the simpliest thing to do is to write an early return.

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

    If you are doing a sub-traversal in a top level path, you can use 2 provided API methods:

    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. });

    Manipulation" class="reference-link">Manipulation

    Replacing a node" class="reference-link">Replacing a node

    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. }

    Replacing a node with multiple nodes" class="reference-link">Replacing a node with multiple nodes

    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. }

    Note: When replacing an expression with multiple nodes, they must be statements. This is because Babel uses heuristics extensively when replacing nodes which means that you can do some pretty crazy transformations that would be extremely verbose otherwise.

    Replacing a node with a source string" class="reference-link">Replacing a node with a source string

    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. }

    Note: It’s not recommended to use this API unless you’re dealing with dynamic source strings, otherwise it’s more efficient to parse the code outside of the visitor.

    Inserting a sibling node" class="reference-link">Inserting a sibling node

    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.";

    Note: This should always be a statement or an array of statements. This uses the same heuristics mentioned in Replacing a node with multiple nodes.

    Inserting into a container" class="reference-link">Inserting into a container

    If you want to insert into an AST node that is an array like body. Similar to insertBefore/insertAfter, except that you have to specify the listKey, which is usually body.

    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. }

    Removing a node" class="reference-link">Removing a node

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

    Replacing a parent" class="reference-link">Replacing a parent

    Just call replaceWith with the parentPath: path.parentPath

    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. }
    1. function square(n) {
    2. - return n * n;
    3. + "Anyway the wind blows, doesn't really matter to me, to me.";
    4. }

    Removing a parent" class="reference-link">Removing a parent

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

    Scope" class="reference-link">Scope

    Checking if a local variable is bound" class="reference-link">Checking if a local variable is bound

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

    This will walk up the scope tree and check for that particular binding.

    You can also check if a scope has its own binding:

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

    Generating a UID" class="reference-link">Generating a UID

    This will generate an identifier that doesn’t collide with any locally defined variables.

    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. }

    Pushing a variable declaration to a parent scope" class="reference-link">Pushing a variable declaration to a parent scope

    Sometimes you may want to push a VariableDeclaration so you can assign to it.

    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. }
    1. - function square(n) {
    2. + var _square = function square(n) {
    3. return n * n;
    4. - }
    5. + };

    Rename a binding and its references" class="reference-link">Rename a binding and its references

    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. }

    Alternatively, you can rename a binding to a generated unique identifier:

    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. }