- 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
- Visiting" level="2">Visiting
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
// the BinaryExpression AST node has properties: `left`, `right`, `operator`
BinaryExpression(path) {
path.node.left;
path.node.right;
path.node.operator;
}
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.
BinaryExpression(path) {
path.get('left');
}
Program(path) {
path.get('body.0');
}
You can’t current use get
on a Container (the body
array of a BlockStatement
), but you chain the dot syntax instead.
export default function f() {
return bar;
}
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.
ExportDefaultDeclaration(path) {
path.get("declaration.body.body.0");
}
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:
BinaryExpression(path) {
if (t.isIdentifier(path.node.left)) {
// ...
}
}
You can also do a shallow check for properties on that node:
BinaryExpression(path) {
if (t.isIdentifier(path.node.left, { name: "n" })) {
// ...
}
}
This is functionally equivalent to:
BinaryExpression(path) {
if (
path.node.left != null &&
path.node.left.type === "Identifier" &&
path.node.left.name === "n"
) {
// ...
}
}
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:
BinaryExpression(path) {
if (path.get('left').isIdentifier({ name: "n" })) {
// ...
}
}
is equivalent to doing:
BinaryExpression(path) {
if (t.isIdentifier(path.node.left, { name: "n" })) {
// ...
}
}
Check if an identifier is referenced" class="reference-link">Check if an identifier is referenced
Identifier(path) {
if (path.isReferencedIdentifier()) {
// ...
}
}
Alternatively:
Identifier(path) {
if (t.isReferenced(path.node, path.parent)) {
// ...
}
}
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 NodePath
s of all the parents.
When the callback
returns a truthy value, we return that NodePath
.
path.findParent((path) => path.isObjectExpression());
If the current path should be included as well:
path.find((path) => path.isObjectExpression());
Find the closest parent function or program:
path.getFunctionParent();
Walk up the tree until we hit a parent node path in a list
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.
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]
path.getPrevSibling() // path(undefined) *
path.getNextSibling() // pathB
path.getAllPrevSiblings() // []
path.getAllNextSiblings() // [pathB, pathC]
}
}
};
}
path(undefined)
is aNodePath
where thepath.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.
BinaryExpression(path) {
if (path.node.operator !== '**') return;
}
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.
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
}
});
Manipulation" class="reference-link">Manipulation
Replacing a node" class="reference-link">Replacing a node
BinaryExpression(path) {
path.replaceWith(
t.binaryExpression("**", path.node.left, t.numberLiteral(2))
);
}
function square(n) {
- return n * n;
+ return n ** 2;
}
Replacing a node with multiple nodes" class="reference-link">Replacing a node with multiple nodes
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)";
}
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
FunctionDeclaration(path) {
path.replaceWithSourceString(`function add(a, b) {
return a + b;
}`);
}
- function square(n) {
- return n * n;
+ function add(a, b) {
+ return a + b;
}
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
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.";
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
.
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"
}
}
Removing a node" class="reference-link">Removing a node
FunctionDeclaration(path) {
path.remove();
}
- function square(n) {
- return n * n;
- }
Replacing a parent" class="reference-link">Replacing a parent
Just call replaceWith
with the parentPath: path.parentPath
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.";
}
Removing a parent" class="reference-link">Removing a parent
BinaryExpression(path) {
path.parentPath.remove();
}
function square(n) {
- return n * n;
}
Scope" class="reference-link">Scope
Checking if a local variable is bound" class="reference-link">Checking if a local variable is bound
FunctionDeclaration(path) {
if (path.scope.hasBinding("n")) {
// ...
}
}
This will walk up the scope tree and check for that particular binding.
You can also check if a scope has its own binding:
FunctionDeclaration(path) {
if (path.scope.hasOwnBinding("n")) {
// ...
}
}
Generating a UID" class="reference-link">Generating a UID
This will generate an identifier that doesn’t collide with any locally defined variables.
FunctionDeclaration(path) {
path.scope.generateUidIdentifier("uid");
// Node { type: "Identifier", name: "_uid" }
path.scope.generateUidIdentifier("uid");
// Node { type: "Identifier", name: "_uid2" }
}
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.
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;
- }
+ };
Rename a binding and its references" class="reference-link">Rename a binding and its references
FunctionDeclaration(path) {
path.scope.rename("n", "x");
}
- function square(n) {
- return n * n;
+ function square(x) {
+ return x * x;
}
Alternatively, you can rename a binding to a generated unique identifier:
FunctionDeclaration(path) {
path.scope.rename("n");
}
- function square(n) {
- return n * n;
+ function square(_n) {
+ return _n * _n;
}