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
}
});
node
Node
The node associated with the NodePath
type
string
Type of the node that is associated the NodePath
key
string | number | null
The 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
}
}
});
listKey
string | null
If 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
}
}
});
removed
boolean
If the node has been removed from its parent
parentPath
NodePath
The parent path of the current path
parent
Node
The parent node of the current path
container
Node | Node[] | null
The 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
}
}
});
scope
Scope
Scope
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 true
Starting 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 true
Starting 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, true
Checks 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, true
Checks 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
}
]
});