You can traverse the AST using the traverse
function
const { traverse } = require('estree-toolkit');
traverse(ast, visitors, state);
As you can see traverse
function takes three arguments - ast
, visitors
, state
.
ast
: <ESTree.Node
> You would pass the AST that you want to traverse. As you may have guessed,
the AST has to be ESTree compliant.
visitors
: <Visitors
> An JavaScript object containing node type as
its keys and a visitor as its value. If you want same visitor for more than
one node type then you have to separate them using |
(pipe symbol).
state
: <any
> This can be anything that can store state in it, preferably an JavaScript object. This
would get passed to the visitor function, and the visitor function can modify it.
Visitors is just a JavaScript object containing any node type as its key and visitor as its value.
// Base
const visitors = {
<Node type>: <Visitor function for the node type>
}
// Eg.
const visitors = {
Identifier() {},
Literal() {},
Program() {},
// more visitors...
}
Visitor can be a function or an object containing enter
and leave
function.
// A single function
const visitors = {
Identifier() { /* Gets called when the traverser enters an Identifier node */ }
}
// An object containing enter and leave functions
const visitors = {
Identifier: {
enter() { /* Gets called when the traverser enters an Identifier node */ },
leave() { /* Gets called when the traverser leaves an Identifier node */ }
}
}
// Basically this is
const visitors = {
Identifier() { ... }
}
// same as -
const visitors = {
Identifier: { enter() { ... } }
}
Visitor function is the function that gets called when the traverser enters or leaves a node
const visitors = {
// This is our visitor function
Identifier(path, state) => {}
}
const visitors = {
Identifier: {
// `enter` and `leave` both are visitor function
enter(path, state) {},
leave(path, state) {}
}
}
You would get two parameters in visitor function - path
and state
path
: <NodePath
> This would be a NodePath for the node that the traverser is currently
traversing.
state
: <any
> This would be the state that has been passed to the traverse
function.
See this for more information.
Also, if your function is bindable (not an arrow function), you would have access to the visitor
context using this
keyword. Visitor context includes a special stop
method, that you can
use to completely stop the traversal.
const visitors = {
Identifier(path, state) {
this // <-- `this` is the visitor context
this.stop() // would stop the traversal
}
}
You can pass options to the traverse function, by attaching options object to $
key
in the visitors object.
const options = {
scope: true,
validateNodes: true
}
traverse(ast, {
$: options, // <-- This is how you pass traverse options
Literal() {},
// ... and other visitors
});
For now, therese are the available options
scope
: <boolean
> If scope tracking should be enabled.
validateNodes
: <boolean
> If the new nodes should be validated, when new
nodes are inserted into the AST. Generally, you wouldn't need it, but if your
AST uses modified nodes then you may need to disable node validation.
cloneFunction
: <(node: any) => any
> Function which will be used to clone nodes (basically JS objects)
using NodePath.cloneNode
. Cloning should be deep cloning and not shallow cloning.
By default it uses structuredClone
.
Also check this out for learning more about why is it important to clone a node.
This is what you would need in most cases
const ast = {
type: 'ExpressionStatement',
expression: {
type: 'ArrayExpression',
elements: [
{ type: 'Identifier', name: 'a' },
{ type: 'Identifier', name: 'b' },
{ type: 'Identifier', name: 'c' },
{ type: 'Identifier', name: 'd' }
]
}
}
traverse(ast, {
Identifier(path) {
// `path` is the NodePath object of the currently traversing node
console.log('Name is -', path.node.name);
}
});
Name is - a
Name is - b
Name is - c
Name is - d
You can share a visitor with two or more node types using this shorthand syntax,
simply by separating them with |
const ast = { /* Check AST tab */ }
traverse(ast, {
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression': (path) => {
console.log(path.type + ':');
path.node.params.forEach((param) => {
if (param.type === 'Identifier') {
console.log(param.name);
}
});
console.log();
}
});
FunctionDeclaration:
a
b
c
FunctionExpression:
e
f
g
ArrowFunctionExpression:
h
i
j
You can use states for more abstract code. Using states you can increase reusability of your code.
const ast1 = {
type: 'ExpressionStatement',
expression: {
type: 'ArrayExpression',
elements: [
{ type: 'Identifier', name: 'a' },
{ type: 'Identifier', name: 'b' },
{ type: 'Identifier', name: 'c' },
{ type: 'Identifier', name: 'd' }
]
}
}
const ast2 = {
type: 'ExpressionStatement',
expression: {
type: 'ArrayExpression',
elements: [
{ type: 'Identifier', name: 'e' },
{ type: 'Identifier', name: 'f' }
]
}
}
const visitors = {
Identifier(path, state) {
// `state` is the state that has been passed in the `traverse` function
state.count++;
state.names.push(path.node.name);
}
}
const data1 = {
count: 0,
names: []
}
const data2 = {
count: 0,
names: []
}
traverse(ast1, visitors, data1);
traverse(ast2, visitors, data2);
console.log(data1);
console.log(data2);
// Yay! We didn't have to write `visitors` two times.
// Just using states, we have increased our code's reusability.
{ count: 4, names: [ 'a', 'b', 'c', 'd' ] }
{ count: 2, names: [ 'e', 'f' ] }
You can stop the traversal using visitor context's stop
method.
const visitors = {
Literal() {
this // { stop() {...} }
}
}
const visitors = {
Literal: () => {
this // unknown, can be anything
}
}
const ast = {
type: 'ExpressionStatement',
expression: {
type: 'ArrayExpression',
elements: [
{ type: 'Identifier', name: 'a' },
{ type: 'Identifier', name: 'b' },
{ type: 'Identifier', name: 'c' },
{ type: 'Identifier', name: 'd' },
{ type: 'Identifier', name: 'e' },
{ type: 'Identifier', name: 'f' }
]
}
}
traverse(ast, {
Identifier(path) {
console.log(path.node.name);
if (path.node.name === 'c') {
this.stop();
console.log('Stopped!');
}
}
});
a
b
c
Stopped!
stop()
at enter
visitor function, the consequent leave
visitor function would not get called.