Utilities for duplicating AST nodes with various levels of depth and location information handling. Cloning is essential when transforming ASTs to avoid modifying original nodes.
The main cloning function with configurable depth options.
/**
* Clone an AST node with optional deep cloning
* @param node - Node to clone
* @param deep - Whether to perform deep clone (default: true)
* @param withoutLoc - Whether to exclude location info (default: false)
* @returns Cloned node
*/
function cloneNode<T extends t.Node>(
node: T,
deep?: boolean,
withoutLoc?: boolean
): T;@deprecated Use t.cloneNode instead.
Alias for shallow cloning (backwards compatibility).
/**
* Shallow clone of an AST node (alias for cloneNode with deep=false)
* @deprecated Use t.cloneNode instead.
* @param node - Node to clone
* @returns Shallow cloned node
*/
function clone<T extends t.Node>(node: T): T;Performs a complete deep clone including all nested nodes.
/**
* Perform deep clone of AST node and all children
* @param node - Node to deep clone
* @returns Deep cloned node with all children cloned
*/
function cloneDeep<T extends t.Node>(node: T): T;Deep clones while removing all location information (useful for generated code).
/**
* Deep clone AST node without location information
* @param node - Node to clone
* @returns Deep cloned node without start/end/loc properties
*/
function cloneDeepWithoutLoc<T extends t.Node>(node: T): T;Clones node while removing location information but preserving structure.
/**
* Clone node without location information (start, end, loc)
* @param node - Node to clone
* @returns Cloned node without location properties
*/
function cloneWithoutLoc<T extends t.Node>(node: T): T;Location information includes:
start: Starting position in sourceend: Ending position in sourceloc: Location object with line/column informationrange: Source range arrayShallow Clone (clone, cloneNode(node, false)):
Deep Clone (cloneDeep, cloneNode(node, true)):
All cloning functions preserve:
import * as t from "@babel/types";
const original = t.identifier("myVar");
original.typeAnnotation = t.tsTypeAnnotation(t.tsStringKeyword());
// Shallow clone - shares type annotation reference
const shallow = t.clone(original);
shallow.name = "newVar";
console.log(original.name); // Still "myVar"
console.log(shallow.typeAnnotation === original.typeAnnotation); // true
// Deep clone - independent copy
const deep = t.cloneDeep(original);
deep.name = "anotherVar";
console.log(deep.typeAnnotation === original.typeAnnotation); // false// Node with location info
const nodeWithLoc = t.identifier("test");
nodeWithLoc.start = 0;
nodeWithLoc.end = 4;
nodeWithLoc.loc = {
start: { line: 1, column: 0 },
end: { line: 1, column: 4 }
};
// Clone with location info preserved
const withLoc = t.cloneNode(nodeWithLoc);
console.log(withLoc.start); // 0
console.log(withLoc.loc); // Location object
// Clone without location info
const withoutLoc = t.cloneWithoutLoc(nodeWithLoc);
console.log(withoutLoc.start); // undefined
console.log(withoutLoc.loc); // undefined
console.log(withoutLoc.name); // "test" - other properties preserved// Complex node with nested children
const binaryExpr = t.binaryExpression(
"+",
t.identifier("a"),
t.callExpression(t.identifier("func"), [t.numericLiteral(42)])
);
// Shallow clone - children are shared
const shallowClone = t.clone(binaryExpr);
shallowClone.left.name = "modified";
console.log(binaryExpr.left.name); // "modified" - original affected!
// Deep clone - children are independent
const deepClone = t.cloneDeep(binaryExpr);
deepClone.left.name = "independent";
console.log(binaryExpr.left.name); // Still "a" - original unaffected// Function declaration with body
const funcDecl = t.functionDeclaration(
t.identifier("myFunc"),
[t.identifier("param")],
t.blockStatement([
t.returnStatement(t.identifier("param"))
])
);
// Deep clone for transformation
const clonedFunc = t.cloneDeep(funcDecl);
clonedFunc.id.name = "clonedFunc";
clonedFunc.params[0].name = "newParam";
// Original remains unchanged
console.log(funcDecl.id.name); // "myFunc"
console.log(funcDecl.params[0].name); // "param"// Array expression
const arrayExpr = t.arrayExpression([
t.stringLiteral("hello"),
t.numericLiteral(42),
t.identifier("variable")
]);
// Clone array - elements are shared in shallow clone
const shallowArray = t.clone(arrayExpr);
shallowArray.elements[0].value = "modified";
console.log(arrayExpr.elements[0].value); // "modified"
// Deep clone array - elements are independent
const deepArray = t.cloneDeep(arrayExpr);
deepArray.elements[0].value = "independent";
console.log(arrayExpr.elements[0].value); // Still "hello"
// Object expression
const objExpr = t.objectExpression([
t.objectProperty(t.identifier("key"), t.stringLiteral("value")),
t.objectMethod(
"method",
t.identifier("method"),
[],
t.blockStatement([])
)
]);
const clonedObj = t.cloneDeep(objExpr);
// Modify without affecting original
clonedObj.properties[0].key.name = "newKey";// Node with comments
const nodeWithComments = t.identifier("commented");
nodeWithComments.leadingComments = [
{ type: "CommentLine", value: " This is a comment" }
];
nodeWithComments.trailingComments = [
{ type: "CommentBlock", value: "* Another comment *" }
];
// Comments are preserved in cloning
const clonedWithComments = t.cloneDeep(nodeWithComments);
console.log(clonedWithComments.leadingComments); // Comment array
console.log(clonedWithComments.trailingComments); // Comment array
// Comments removed with location info
const clonedWithoutLoc = t.cloneDeepWithoutLoc(nodeWithComments);
console.log(clonedWithoutLoc.leadingComments); // Still present - only loc removed// Template for code generation
const template = t.functionExpression(
null,
[t.identifier("PARAM_NAME")],
t.blockStatement([
t.returnStatement(t.identifier("RETURN_VALUE"))
])
);
// Generate multiple functions from template
function generateFunction(paramName: string, returnValue: string) {
const func = t.cloneDeep(template);
// Replace placeholders
func.params[0].name = paramName;
const returnStmt = func.body.body[0] as t.ReturnStatement;
(returnStmt.argument as t.Identifier).name = returnValue;
return func;
}
const func1 = generateFunction("input", "output");
const func2 = generateFunction("data", "result");
// Each function is independent
console.log(func1.params[0].name); // "input"
console.log(func2.params[0].name); // "data"
console.log(template.params[0].name); // "PARAM_NAME" - unchanged// For large ASTs, consider shallow cloning when possible
const largeAST = /* complex nested structure */;
// Fast - only clones top-level node
const quickClone = t.clone(largeAST);
// Slower - clones entire tree
const fullClone = t.cloneDeep(largeAST);
// Use shallow cloning when you only need to modify top-level properties
function renameFunction(funcDecl: t.FunctionDeclaration, newName: string) {
const cloned = t.clone(funcDecl);
cloned.id = t.identifier(newName);
return cloned; // Body and params are shared but unchanged
}
// Use deep cloning when modifying nested structures
function transformFunction(funcDecl: t.FunctionDeclaration) {
const cloned = t.cloneDeep(funcDecl);
// Safe to modify any part of the cloned function
cloned.body.body.push(t.returnStatement());
return cloned;
}import { traverse } from "@babel/types";
// Clone nodes during transformation
traverse(ast, {
Identifier(path) {
// Clone before modifying to preserve original AST
const cloned = t.cloneNode(path.node);
cloned.name = cloned.name.toUpperCase();
path.replaceWith(cloned);
},
BinaryExpression(path) {
// Deep clone for complex modifications
const cloned = t.cloneDeep(path.node);
// Swap operands
const temp = cloned.left;
cloned.left = cloned.right;
cloned.right = temp;
path.replaceWith(cloned);
}
});