ECMAScript JS AST traversal functions
npx @tessl/cli install tessl/npm-estraverse@5.3.0Estraverse is a JavaScript library that provides ECMAScript Abstract Syntax Tree (AST) traversal functionality. It offers a comprehensive API for walking through JavaScript code structures with visitor patterns, supporting tree modification, node replacement, and custom traversal rules. This library is essential for building JavaScript tools like linters, code transformers, and static analysis tools.
npm install estraverseconst estraverse = require('estraverse');ES Modules:
import * as estraverse from 'estraverse';Individual imports:
const { traverse, replace, Syntax, VisitorOption, VisitorKeys, Controller, attachComments, cloneEnvironment } = require('estraverse');const estraverse = require('estraverse');
// Basic traversal - visit all nodes in an AST
estraverse.traverse(ast, {
enter: function (node, parent) {
console.log('Entering:', node.type);
},
leave: function (node, parent) {
console.log('Leaving:', node.type);
}
});
// Skip traversing function bodies
estraverse.traverse(ast, {
enter: function (node, parent) {
if (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration') {
return estraverse.VisitorOption.Skip;
}
}
});
// Replace nodes during traversal
const result = estraverse.replace(ast, {
enter: function (node, parent) {
if (node.type === 'Literal' && typeof node.value === 'string') {
return {
type: 'Literal',
value: node.value.toUpperCase(),
raw: '"' + node.value.toUpperCase() + '"'
};
}
}
});Estraverse is built around several key components that work together to provide flexible AST traversal:
enter and leave callbacks that are invoked for each node during traversalController class provides fine-grained control over traversal with methods for path tracking, parent access, and flow controlCore AST traversal functionality using the visitor pattern.
/**
* Traverse an AST with a visitor object
* @param {Object} root - The root AST node to traverse
* @param {Object} visitor - Visitor object with enter/leave callbacks
* @returns {void}
*/
function traverse(root, visitor);
/**
* Traverse an AST and replace nodes based on visitor returns
* @param {Object} root - The root AST node to traverse
* @param {Object} visitor - Visitor object with enter/leave callbacks that can return replacement nodes
* @returns {Object} - The modified AST
*/
function replace(root, visitor);Visitor Object Structure:
interface Visitor {
/** Called when entering a node during traversal */
enter?: (node: Object, parent: Object) => any;
/** Called when leaving a node during traversal */
leave?: (node: Object, parent: Object) => any;
/** Custom visitor keys for unknown node types */
keys?: Object;
/** Fallback strategy for unknown nodes ('iteration' or function) */
fallback?: string | function;
}Visitor Return Values:
estraverse.VisitorOption.Break - Stop traversal entirelyestraverse.VisitorOption.Skip - Skip child nodes of current nodeestraverse.VisitorOption.Remove - Remove current node (replace() only)undefined - Continue normal traversalManual traversal control with fine-grained operation management.
/**
* Controller class for manual AST traversal
* @constructor
*/
function Controller();Controller Instance Methods:
interface ControllerInstance {
/** Get the property path array from root to current node (returns null for root) */
path(): string[] | null;
/** Get the type of the current node */
type(): string;
/** Get array of parent elements */
parents(): Object[];
/** Get the current node */
current(): Object;
/** Skip child nodes of current node */
skip(): void;
/** Break traversal entirely */
break(): void;
/** Remove current node */
remove(): void;
/** Notify control flow (internal method used by skip/break/remove) */
notify(flag: Object): void;
/** Traverse using this controller instance */
traverse(root: Object, visitor: Object): void;
/** Replace traverse using this controller instance */
replace(root: Object, visitor: Object): Object;
}When using Controller, visitor functions can access controller methods via this:
const controller = new estraverse.Controller();
controller.traverse(ast, {
enter: function (node, parent) {
if (node.type === 'FunctionDeclaration') {
this.skip(); // Skip function body
}
console.log('Path:', this.path());
console.log('Parents:', this.parents().length);
}
});Enumeration of all supported ECMAScript AST node types.
/**
* Object containing constants for all ECMAScript AST node types
*/
const Syntax: {
AssignmentExpression: 'AssignmentExpression';
AssignmentPattern: 'AssignmentPattern';
ArrayExpression: 'ArrayExpression';
ArrayPattern: 'ArrayPattern';
ArrowFunctionExpression: 'ArrowFunctionExpression';
AwaitExpression: 'AwaitExpression';
BlockStatement: 'BlockStatement';
BinaryExpression: 'BinaryExpression';
BreakStatement: 'BreakStatement';
CallExpression: 'CallExpression';
CatchClause: 'CatchClause';
ChainExpression: 'ChainExpression';
ClassBody: 'ClassBody';
ClassDeclaration: 'ClassDeclaration';
ClassExpression: 'ClassExpression';
ComprehensionBlock: 'ComprehensionBlock';
ComprehensionExpression: 'ComprehensionExpression';
ConditionalExpression: 'ConditionalExpression';
ContinueStatement: 'ContinueStatement';
DebuggerStatement: 'DebuggerStatement';
DirectiveStatement: 'DirectiveStatement';
DoWhileStatement: 'DoWhileStatement';
EmptyStatement: 'EmptyStatement';
ExportAllDeclaration: 'ExportAllDeclaration';
ExportDefaultDeclaration: 'ExportDefaultDeclaration';
ExportNamedDeclaration: 'ExportNamedDeclaration';
ExportSpecifier: 'ExportSpecifier';
ExpressionStatement: 'ExpressionStatement';
ForStatement: 'ForStatement';
ForInStatement: 'ForInStatement';
ForOfStatement: 'ForOfStatement';
FunctionDeclaration: 'FunctionDeclaration';
FunctionExpression: 'FunctionExpression';
GeneratorExpression: 'GeneratorExpression';
Identifier: 'Identifier';
IfStatement: 'IfStatement';
ImportExpression: 'ImportExpression';
ImportDeclaration: 'ImportDeclaration';
ImportDefaultSpecifier: 'ImportDefaultSpecifier';
ImportNamespaceSpecifier: 'ImportNamespaceSpecifier';
ImportSpecifier: 'ImportSpecifier';
Literal: 'Literal';
LabeledStatement: 'LabeledStatement';
LogicalExpression: 'LogicalExpression';
MemberExpression: 'MemberExpression';
MetaProperty: 'MetaProperty';
MethodDefinition: 'MethodDefinition';
ModuleSpecifier: 'ModuleSpecifier';
NewExpression: 'NewExpression';
ObjectExpression: 'ObjectExpression';
ObjectPattern: 'ObjectPattern';
PrivateIdentifier: 'PrivateIdentifier';
Program: 'Program';
Property: 'Property';
PropertyDefinition: 'PropertyDefinition';
RestElement: 'RestElement';
ReturnStatement: 'ReturnStatement';
SequenceExpression: 'SequenceExpression';
SpreadElement: 'SpreadElement';
Super: 'Super';
SwitchStatement: 'SwitchStatement';
SwitchCase: 'SwitchCase';
TaggedTemplateExpression: 'TaggedTemplateExpression';
TemplateElement: 'TemplateElement';
TemplateLiteral: 'TemplateLiteral';
ThisExpression: 'ThisExpression';
ThrowStatement: 'ThrowStatement';
TryStatement: 'TryStatement';
UnaryExpression: 'UnaryExpression';
UpdateExpression: 'UpdateExpression';
VariableDeclaration: 'VariableDeclaration';
VariableDeclarator: 'VariableDeclarator';
WhileStatement: 'WhileStatement';
WithStatement: 'WithStatement';
YieldExpression: 'YieldExpression';
};Constants for controlling traversal flow from visitor functions.
/**
* Control flow constants for visitor functions
*/
const VisitorOption: {
/** Break traversal entirely */
Break: Object;
/** Skip child nodes of current node */
Skip: Object;
/** Remove current node (replace() only) */
Remove: Object;
};Default mapping of AST node types to their traversable child property names.
/**
* Default visitor keys mapping node types to child property names
*/
const VisitorKeys: {
AssignmentExpression: ['left', 'right'];
AssignmentPattern: ['left', 'right'];
ArrayExpression: ['elements'];
ArrayPattern: ['elements'];
ArrowFunctionExpression: ['params', 'body'];
AwaitExpression: ['argument'];
BlockStatement: ['body'];
BinaryExpression: ['left', 'right'];
BreakStatement: ['label'];
CallExpression: ['callee', 'arguments'];
CatchClause: ['param', 'body'];
ChainExpression: ['expression'];
ClassBody: ['body'];
ClassDeclaration: ['id', 'superClass', 'body'];
ClassExpression: ['id', 'superClass', 'body'];
ComprehensionBlock: ['left', 'right'];
ComprehensionExpression: ['blocks', 'filter', 'body'];
ConditionalExpression: ['test', 'consequent', 'alternate'];
ContinueStatement: ['label'];
DebuggerStatement: [];
DirectiveStatement: [];
DoWhileStatement: ['body', 'test'];
EmptyStatement: [];
ExportAllDeclaration: ['source'];
ExportDefaultDeclaration: ['declaration'];
ExportNamedDeclaration: ['declaration', 'specifiers', 'source'];
ExportSpecifier: ['exported', 'local'];
ExpressionStatement: ['expression'];
ForStatement: ['init', 'test', 'update', 'body'];
ForInStatement: ['left', 'right', 'body'];
ForOfStatement: ['left', 'right', 'body'];
FunctionDeclaration: ['id', 'params', 'body'];
FunctionExpression: ['id', 'params', 'body'];
GeneratorExpression: ['blocks', 'filter', 'body'];
Identifier: [];
IfStatement: ['test', 'consequent', 'alternate'];
ImportExpression: ['source'];
ImportDeclaration: ['specifiers', 'source'];
ImportDefaultSpecifier: ['local'];
ImportNamespaceSpecifier: ['local'];
ImportSpecifier: ['imported', 'local'];
Literal: [];
LabeledStatement: ['label', 'body'];
LogicalExpression: ['left', 'right'];
MemberExpression: ['object', 'property'];
MetaProperty: ['meta', 'property'];
MethodDefinition: ['key', 'value'];
ModuleSpecifier: [];
NewExpression: ['callee', 'arguments'];
ObjectExpression: ['properties'];
ObjectPattern: ['properties'];
PrivateIdentifier: [];
Program: ['body'];
Property: ['key', 'value'];
PropertyDefinition: ['key', 'value'];
RestElement: ['argument'];
ReturnStatement: ['argument'];
SequenceExpression: ['expressions'];
SpreadElement: ['argument'];
Super: [];
SwitchStatement: ['discriminant', 'cases'];
SwitchCase: ['test', 'consequent'];
TaggedTemplateExpression: ['tag', 'quasi'];
TemplateElement: [];
TemplateLiteral: ['quasis', 'expressions'];
ThisExpression: [];
ThrowStatement: ['argument'];
TryStatement: ['block', 'handler', 'finalizer'];
UnaryExpression: ['argument'];
UpdateExpression: ['argument'];
VariableDeclaration: ['declarations'];
VariableDeclarator: ['id', 'init'];
WhileStatement: ['test', 'body'];
WithStatement: ['object', 'body'];
YieldExpression: ['argument'];
};Utility for attaching comments to AST nodes based on their position.
/**
* Attach comments to AST nodes based on position relative to tokens
* @param {Object} tree - The AST tree to attach comments to
* @param {Array} providedComments - Array of comment objects with range information
* @param {Array} tokens - Array of token objects with range information
* @returns {Object} - The tree with attached comments
*/
function attachComments(tree, providedComments, tokens);Comments are attached as leadingComments and trailingComments properties on AST nodes.
Create isolated estraverse environments for custom configurations.
/**
* Create a new isolated estraverse environment
* @returns {Object} - New estraverse instance with independent configuration
*/
function cloneEnvironment();Extend estraverse to handle custom AST node types:
// Define custom visitor keys for unknown node types
estraverse.traverse(customAst, {
enter: function (node) {
console.log('Visiting:', node.type);
},
keys: {
// CustomNodeType: ['child1', 'child2']
TestExpression: ['argument']
}
});Control behavior when encountering unknown node types:
// Use iteration fallback to explore all properties
estraverse.traverse(ast, {
enter: function (node) { /* ... */ },
fallback: 'iteration'
});
// Use custom function fallback for fine-grained control
estraverse.traverse(ast, {
enter: function (node) { /* ... */ },
fallback: function(node) {
return Object.keys(node).filter(key => key !== 'parent');
}
});Track the path to current node during traversal:
const controller = new estraverse.Controller();
controller.traverse(ast, {
enter: function (node, parent) {
const path = this.path();
const depth = this.parents().length;
console.log(`Node ${node.type} at path: ${path.join('.')} (depth: ${depth})`);
}
});