A lightweight utility for traversing ESTree AST nodes with generator functions.
- Traverse ESTree-compatible AST nodes using generator functions
- Access parent nodes and path information during traversal
- Support for both entry and exit phase during node traversal
- TypeScript support
While estree-walker is a popular library for traversing ESTree ASTs using callback functions, estree-glide takes a different approach using generator functions:
Generator functions in estree-glide provide several advantages:
- More intuitive control flow with
yield
statements - Ability to return processed values from child nodes
- Natural separation of entry and exit phases
- Cleaner code without method binding or
this
context
the generator function has two phases: the enter phase and the exit phase.
The whole execution is like this:
- The enter phase code runs before
yield
- yield is called and the child nodes are traversed, recursively
- run the exit phase code is after
yield
.
Enter phase is executed when the node is first visited. The yield
statement is used to pause the execution of the generator function and pass control to the child nodes. The child nodes will be traversed, and when they are done, control will return to the parent node, and the code after yield
will be executed
This is a contrived simple example to illustrate the traversal function in estree-glide:
function* traverseAST(node: Node, accumulator: { enter: number, exit: number }) {
// code before yield is equivalent to the enter phase in estree-walker
accumulator.enter++
// proceed traversal to child nodes
yield
// code after yield is equivalent to the exit phase in estree-walker
accumulator.exit++
}
This is equivalent to estree-walker:
import { walk } from 'estree-walker'
let accumulator = { enter: 0, exit: 0 }
walk(ast, {
enter(node) {
accumulator.enter++
},
leave(node) {
accumulator.exit++
}
})
Note in estree-glide we don't need to rely on the global variable accumulator
.
The first argument is the current node during the traversal, and the second argument is an accumulator object.
The accumulator object can be used to store information during the traversal, in this case, we are counting the number of nodes entered and exited. A more realistic example would be to collect the node definitions in a scope or collecting the usage of a variable.
To execute the traversal function
import { traverse } from 'estree-glide'
import { parse } from '@babel/parser'
const ast = parse("const a = 1", {
sourceType: 'module',
plugins: ['typescript', 'jsx'],
})
// initial accumulator value
const accumulator = { enter: 0, exit: 0 }
traverse(ast.program, traverseAST, accumulator)
Given the recursive nature of the traversal, it is quite often we need to use a new accumlator value for the child nodes. In estree-glide, we can use the yield
statement to pass new accumulator to descendent node traversal.
For example, we can create new scope for function declarations and pass it to the child nodes.
function* collectScope(node: Node, scope: Scope) {
if (node.type === 'VariableDeclaration') {
// collect variable declarations in the current scope
for (const declaration of node.declarations) {
scope.declarations.push(declaration)
}
} else if (node.type === 'FunctionDeclaration') {
const newScope = new Scope(node)
scope.children.push(newScope)
// pass the new scope to child nodes
yield newScope
}
}
Given this source code
function foo() {
var a = 1
function bar() { var b = 2 }
}
It will create a scope tree like this:
Scope {
declarations: [VariableDeclaration],
children: [
Scope {
declarations: [VariableDeclaration],
children: []
}
]
}