JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator
—
Tools for traversing and modifying Abstract Syntax Trees with type safety using the ast-types library integration.
Traverse and potentially modify an abstract syntax tree using a convenient visitor syntax.
/**
* Traverse and modify AST nodes using visitor pattern
* @param ast - The AST node to traverse
* @param visitor - Visitor object with node-specific methods
* @returns The modified AST
*/
function visit(ast: types.ASTNode, visitor: Visitor): types.ASTNode;
interface Visitor {
[methodName: string]: (path: NodePath) => any;
}
interface NodePath {
/** The current AST node */
value: any;
/** Parent path in the traversal */
parent: NodePath | null;
/** Name of the property this node represents */
name: string | number | null;
/** Continue traversing child nodes */
traverse(path?: NodePath): void;
/** Replace current node with new node */
replace(node: any): void;
/** Insert nodes before current node */
insertBefore(...nodes: any[]): void;
/** Insert nodes after current node */
insertAfter(...nodes: any[]): void;
/** Remove current node */
prune(): void;
}Usage Examples:
import { parse, visit, print } from "recast";
const ast = parse(sourceCode);
// Transform all identifier names to uppercase
visit(ast, {
visitIdentifier(path) {
const node = path.value;
node.name = node.name.toUpperCase();
this.traverse(path);
}
});
// Replace function declarations with arrow functions
visit(ast, {
visitFunctionDeclaration(path) {
const node = path.value;
const arrowFunction = types.builders.variableDeclaration("const", [
types.builders.variableDeclarator(
node.id,
types.builders.arrowFunctionExpression(node.params, node.body)
)
]);
path.replace(arrowFunction);
return false; // Skip traversing children
}
});Access to the complete ast-types library for AST construction and validation.
/**
* AST types namespace providing builders and validators
*/
namespace types {
/** AST node constructor functions */
const builders: Builders;
/** Type checking and assertion functions */
const namedTypes: NamedTypes;
/** Built-in type validators */
const builtInTypes: BuiltInTypes;
}
interface Builders {
/** Create identifier node */
identifier(name: string): Identifier;
/** Create literal node */
literal(value: any): Literal;
/** Create function expression */
functionExpression(
id: Identifier | null,
params: Pattern[],
body: BlockStatement
): FunctionExpression;
/** Create variable declaration */
variableDeclaration(
kind: "var" | "let" | "const",
declarations: VariableDeclarator[]
): VariableDeclaration;
/** Create variable declarator */
variableDeclarator(
id: Pattern,
init?: Expression | null
): VariableDeclarator;
/** Create arrow function expression */
arrowFunctionExpression(
params: Pattern[],
body: BlockStatement | Expression
): ArrowFunctionExpression;
/** Create member expression */
memberExpression(
object: Expression,
property: Expression | Identifier,
computed?: boolean
): MemberExpression;
/** Create call expression */
callExpression(
callee: Expression,
arguments: Array<Expression | SpreadElement>
): CallExpression;
// ... many more builder functions
}
interface NamedTypes {
/** Assert node is an Identifier */
Identifier: TypeChecker<Identifier>;
/** Assert node is a FunctionDeclaration */
FunctionDeclaration: TypeChecker<FunctionDeclaration>;
/** Assert node is a VariableDeclaration */
VariableDeclaration: TypeChecker<VariableDeclaration>;
// ... many more type checkers
}
interface TypeChecker<T> {
/** Check if value is of this type */
check(value: any): value is T;
/** Assert value is of this type (throws if not) */
assert(value: any): asserts value is T;
/** Get the type definition */
def: TypeDefinition;
}Usage Examples:
import { parse, types, visit, print } from "recast";
const b = types.builders;
const n = types.namedTypes;
const ast = parse("function add(a, b) { return a + b; }");
visit(ast, {
visitFunctionDeclaration(path) {
const node = path.value;
// Type checking
if (n.FunctionDeclaration.check(node)) {
// Convert to arrow function
const arrowFunc = b.variableDeclaration("const", [
b.variableDeclarator(
node.id,
b.arrowFunctionExpression(node.params, node.body)
)
]);
path.replace(arrowFunc);
}
return false;
}
});Advanced path manipulation for complex AST transformations.
interface NodePath {
/** Get the value at this path */
value: any;
/** Get parent path */
parent: NodePath | null;
/** Get property name/index */
name: string | number | null;
/** Get scope information */
scope: Scope;
/** Navigate to parent path */
parentPath: NodePath | null;
/** Get child paths */
get(...names: Array<string | number>): NodePath;
/** Check if path exists */
has(...names: Array<string | number>): boolean;
/** Replace node at this path */
replace(node: any): void;
/** Insert nodes before current node */
insertBefore(...nodes: any[]): void;
/** Insert nodes after current node */
insertAfter(...nodes: any[]): void;
/** Remove node at this path */
prune(): void;
/** Continue traversal */
traverse(path?: NodePath): void;
}Usage Examples:
import { parse, visit } from "recast";
visit(ast, {
visitCallExpression(path) {
const node = path.value;
// Access function name through path navigation
const calleePath = path.get("callee");
if (calleePath.value.name === "console") {
const propertyPath = path.get("property");
if (propertyPath.value.name === "log") {
// Replace console.log with console.warn
propertyPath.replace(types.builders.identifier("warn"));
}
}
this.traverse(path);
}
});Access to scope information for variable binding analysis.
interface Scope {
/** Parent scope */
parent: Scope | null;
/** Declared bindings in this scope */
bindings: { [name: string]: Binding[] };
/** Scope type */
type: "function" | "block" | "catch" | "with";
/** Check if identifier is bound in this scope */
declares(name: string): boolean;
/** Look up binding in scope chain */
lookup(name: string): Scope | null;
/** Get all names declared in this scope */
getBindingNames(): string[];
}
interface Binding {
/** Identifier node that declares this binding */
identifier: Identifier;
/** Scope where this binding is declared */
scope: Scope;
/** AST path to the binding */
path: NodePath;
}Usage Examples:
import { parse, visit } from "recast";
visit(ast, {
visitIdentifier(path) {
const node = path.value;
const scope = path.scope;
// Check if identifier is bound in current scope
if (scope.declares(node.name)) {
console.log(`${node.name} is declared locally`);
} else {
console.log(`${node.name} is from outer scope`);
}
this.traverse(path);
}
});Converting between different function syntaxes.
// Function declaration to arrow function
visit(ast, {
visitFunctionDeclaration(path) {
const node = path.value;
const arrow = types.builders.variableDeclaration("const", [
types.builders.variableDeclarator(
node.id,
types.builders.arrowFunctionExpression(node.params, node.body)
)
]);
path.replace(arrow);
return false;
}
});Systematically renaming variables throughout the AST.
const renameMap = { oldName: "newName" };
visit(ast, {
visitIdentifier(path) {
const node = path.value;
if (renameMap[node.name]) {
node.name = renameMap[node.name];
}
this.traverse(path);
}
});Adding new statements or expressions to existing code.
visit(ast, {
visitFunctionDeclaration(path) {
const node = path.value;
// Add console.log at start of function
const logStatement = types.builders.expressionStatement(
types.builders.callExpression(
types.builders.memberExpression(
types.builders.identifier("console"),
types.builders.identifier("log")
),
[types.builders.literal(`Entering function ${node.id.name}`)]
)
);
node.body.body.unshift(logStatement);
this.traverse(path);
}
});Install with Tessl CLI
npx tessl i tessl/npm-recast