NodePath is basically a wrapper around any AST node. It provides useful properties and methods
to manipulate the AST.
const { traverse } = require('estree-toolkit');
traverse(ast, {
Identifier(path) {
// `path` is NodePath
}
});nodeNodeThe node associated with the NodePath
typestringType of the node that is associated the NodePath
keystring | number | nullThe current node's key in its parent
const ast = {
type: 'IfStatement',
test: {
type: 'Identifier',
name: 'targetNode'
},
consequent: {
type: 'EmptyStatement'
},
alternate: null
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
path.key === 'test' // => true
}
}
});listKeystring | nullIf this node is part of an array, listKey would be the key of the array in its parent
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'targetNode'
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
path.listKey === 'elements' // => true
path.key === 0 // => true
}
}
});removedbooleanIf the node has been removed from its parent
parentPathNodePathThe parent path of the current path
parentNodeThe parent node of the current path
containerNode | Node[] | nullThe container of the node
In this case container is an array
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'targetNode'
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
path.container === ast.elements // => true
}
}
});In this case container is a node object
const ast = {
type: 'IfStatement',
test: {
type: 'Identifier',
name: 'targetNode'
},
consequent: {
type: 'EmptyStatement'
},
alternate: null
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
path.container === ast // => true
}
}
});scopeScopeScope instance for the scope where the node is located. It's null when scope
tracking is disabled.
cloneNode()Clone the node associated with the path using structuredClone or
the custom clone function provided.
Also check this out for learning more about why is it important to clone a node.
skip()Skips the path from traversal
const ast = {
type: 'IfStatement',
test: {
type: 'Identifier',
name: 'targetNode'
},
consequent: {
type: 'EmptyStatement'
},
alternate: null
};
traverse(ast, {
IfStatement(path) {
path.get('test').skip();
},
Identifier() {
// Never gets called because the path has been skipped
}
});unSkip()Un-skip the path from traversal. Whenever un-skipped, the path would be traversed if it's not already traversed
unskip()Alias for unSkip
skipChildren()Skips all children of the path from traversal
unSkipChildren()Un-skip all children of the path from traversal. Whenever un-skipped, children of the path would be traversed if the children is not already traversed
unskipChildren()Alias for unSkipChildren
traverse(visitor, state)visitor: <Visitor> Object to use as the visitorstate: <any> The initial stateTraverse this path and its children with the given visitor and state
findParent(predicate)predicate: <(path: NodePath) => boolean> Callback function to predicate the parentNodePath> The first NodePath for which the predicate function returned trueStarting at the parent path of this path and going up the tree,
returns first parent path where predicate is true
const ast = {
type: 'BlockStatement',
body: [
{
type: 'BlockStatement',
body: [
{
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: 0
}
}
]
}
]
};
traverse(ast, {
Literal(path) {
const blockParent = path.findParent(
(parent) => parent.type === 'BlockStatement'
).node;
blockParent === ast.body[0] // => true, Notice how it is not `ast` and is `ast.body[0]`
}
});find(predicate)predicate: <(path: NodePath) => boolean> Callback function to predicate the parentNodePath> The first NodePath for which the predicate function returned trueStarting from this path and going up the tree,
returns the first path where predicate is true
const ast = {
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: 0
}
};
traverse(ast, {
Literal(path) {
path.find((p) => p.type === 'Literal').node === ast.expression // => true
path.find((p) => p.type === 'ExpressionStatement').node === ast // => true
}
});getFunctionParent()NodePath> The closest function parentGoing up the tree returns the NodePath's closest function parent.
getAncestry()NodePath[]> The array containing all ancestorsGoes up the tree and collects all the ancestors. The node itself is also included in the ancestry.
isAncestorOf(path)path: <NodePath> The path for which the current path might be a ancestorboolean> If the current path is ancestor of given path, trueChecks if the current path is a ancestor of the given path
isDescendantOf(path)path: <NodePath> The path for which the current path might be a descendantboolean> If the current path is descendant of given path, trueChecks if the current path is a descendant of the given path
insertBefore(nodes)nodes: <Node[]> The nodes that would be insertedNodePath[]> An array of NodePaths for the inserted nodesInserts the nodes before the current node
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'targetNode'
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
const newPaths = path.insertBefore([
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
}
]);
// `newPaths` are the paths for the inserted nodes
}
}
});
assert.deepEqual(ast, {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
},
{
type: 'Identifier',
name: 'targetNode'
}
]
});insertAfter(nodes)nodes: <Node[]> The nodes that would be insertedNodePath[]> An array of NodePaths for the inserted nodesInserts the nodes after the current node
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'targetNode'
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
const newPaths = path.insertAfter([
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
}
]);
// `newPaths` are the paths for the inserted nodes
}
}
});
assert.deepEqual(ast, {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'targetNode'
},
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
}
]
});unshiftContainer(listKey, nodes)listKey: <string> The list key of the container where the nodes would be insertednodes: <Node[]> The nodes that would be insertedNodePath[]> An array of NodePaths for the inserted nodesInserts child nodes at the start of the container
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'undefined'
}
]
};
traverse(ast, {
ArrayExpression(path) {
const newPaths = path.unshiftContainer('elements', [
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
}
]);
// `newPaths` are the paths for the inserted nodes
}
});
assert.deepEqual(ast, {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
},
{
type: 'Identifier',
name: 'undefined'
}
]
});pushContainer(listKey, nodes)listKey: <string> The list key of the container where the nodes would be insertednodes: <Node[]> The nodes that would be insertedNodePath[]> An array of NodePaths for the inserted nodesInserts child nodes at the end of the container
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'undefined'
}
]
};
traverse(ast, {
ArrayExpression(path) {
const newPaths = path.pushContainer('elements', [
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
}
]);
// `newPaths` are the paths for the inserted nodes
}
});
assert.deepEqual(ast, {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'undefined'
},
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
}
]
});get(key)key: <string> The key of the child nodeNodePath | NodePath[]> The NodePath/NodePaths of the child node/nodes for the given keyGet the current path's children's path for which the key is key
const ast = {
type: 'IfStatement',
test: {
type: 'Identifier',
name: 'targetNode'
},
consequent: {
type: 'EmptyStatement'
},
alternate: null
};
traverse(ast, {
IfStatement(path) {
const testPath = path.get('test');
testPath.node === ast.test // => true
}
});getSibling(key)key: <string | number> The key of the sibling nodeNodePath | undefined> The NodePath of the sibling nodeGet the path of current path's sibling for which the key is key
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Identifier',
name: 'targetNode'
},
{
type: 'Literal',
value: 16
},
{
type: 'Literal',
value: 5
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
path.getSibling(2).node === ast.elements[2] // => true
}
}
});getOpposite()NodePath> The opposite NodePathGet the opposite path of the current path
const ast = {
type: 'AssignmentExpression',
left: {
type: 'Identifier',
name: 'a'
},
operator: '=',
right: {
type: 'Identifier',
name: 'b'
}
};
traverse(ast, {
Identifier(path) {
if (path.key === 'left') {
path.getOpposite().node === ast.right // => true
}
}
});getPrevSibling()NodePath> The previous NodePathThe the previous sibling's path of the current path
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 16
},
{
type: 'Identifier',
name: 'targetNode'
},
{
type: 'Literal',
value: 5
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
path.getPrevSibling().node === ast.elements[0] // => true
}
}
});getNextSibling()NodePath> The next NodePathThe the next sibling's path of the current path
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 16
},
{
type: 'Identifier',
name: 'targetNode'
},
{
type: 'Literal',
value: 5
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
path.getNextSibling().node === ast.elements[2] // => true
}
}
});getAllPrevSiblings()NodePath[] An array containing paths of next siblingsGet all previous siblings' path of the current path
!!! Returned paths are reversed (they are sorted in how close they are to the current path, see example) !!!
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
},
{
type: 'Identifier',
name: 'targetNode'
},
{
type: 'Literal',
value: 3
},
{
type: 'Literal',
value: 4
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
const prevPaths = path.getAllPrevSiblings();
prevPaths[0].node === ast.elements[1] // => true
prevPaths[1].node === ast.elements[0] // => true
}
}
});getAllNextSiblings()NodePath[]> An array containing paths of previous siblingsGet all next siblings' path of the current path
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
},
{
type: 'Identifier',
name: 'targetNode'
},
{
type: 'Literal',
value: 3
},
{
type: 'Literal',
value: 4
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
const nextPaths = path.getAllNextSiblings();
nextPaths[0].node === ast.elements[3] // => true
nextPaths[1].node === ast.elements[4] // => true
}
}
});has(key)key: <string> The key to search for any kind of valueboolean> If the node has the valueChecks if the path has the specific property. If value of the property is an array, checks if the array is not empty.
const ast = {
type: 'IfStatement',
test: {
type: 'Identifier',
name: 'x'
},
consequent: {
type: 'EmptyStatement'
},
alternate: null
};
traverse(ast, {
IfStatement(path) {
path.has('consequent') // => true
path.has('alternate') // => false
}
});const ast1 = {
type: 'Program',
sourceType: 'module',
body: []
};
traverse(ast1, {
Program(path) {
path.has('body') // => false
// Because the array is empty
}
});
const ast2 = {
type: 'Program',
sourceType: 'module',
body: [
{
type: 'EmptyStatement'
}
]
};
traverse(ast2, {
Program(path) {
path.has('body') // => true
// In this case the array is not empty
}
});is(key)key: <string> The key to search for the valueboolean> If the value is truthyChecks if the path is something
const ast = {
type: 'ObjectExpression',
properties: [
{
type: 'Property',
key: {
type: 'Identifier',
name: 'x'
},
value: {
type: 'Identifier',
name: 'x'
},
kind: 'init',
computed: false,
method: false,
shorthand: true
}
]
};
traverse(ast, {
Property(path) {
if (path.node === ast.properties[0]) {
path.is('shorthand') // => true
path.is('method') // => false
}
}
});remove()Removes the node from the tree
replaceWith(node)node: <Node> The node that the current node would be replace withNodePath> The NodePath of the new nodeRemoves the old node and inserts the new node in the old node's position
const ast = {
type: 'AssignmentExpression',
left: {
type: 'Identifier',
name: 'targetNode'
},
operator: '=',
right: {
type: 'Identifier',
name: 'b'
}
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
const newPath = path.replace({
type: 'Identifier',
name: 'a'
});
// `newPath` is the path for the new node
}
}
});
assert.deepEqual(ast, {
type: 'AssignmentExpression',
left: {
type: 'Identifier',
name: 'a'
},
operator: '=',
right: {
type: 'Identifier',
name: 'b'
}
});replaceWithMultiple(nodes)nodes: <NodePath[]> The nodes that the current node would be replace withNodePath[]> The NodePaths of the new nodesRemoves the old node and inserts the new nodes in the old node's position
const ast = {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 1
},
{
type: 'Identifier',
name: 'targetNode'
},
{
type: 'Literal',
value: 5
}
]
};
traverse(ast, {
Identifier(path) {
if (path.node.name === 'targetNode') {
const newPaths = path.replaceWithMultiple([
{
type: 'Literal',
value: 2
},
{
type: 'Literal',
value: 3
},
{
type: 'Literal',
value: 4
}
]);
// `newPaths` are the paths for the new nodes
}
}
});
assert.deepEqual(ast, {
type: 'ArrayExpression',
elements: [
{
type: 'Literal',
value: 1
},
{
type: 'Literal',
value: 2
},
{
type: 'Literal',
value: 3
},
{
type: 'Literal',
value: 4
},
{
type: 'Literal',
value: 5
}
]
});