Utilities for managing comments on AST nodes. Comments in JavaScript/TypeScript can be leading, trailing, or inner comments, and these functions help manipulate and inherit comment attachments during AST transformations.
Adds a single comment to a node in the specified position.
/**
* Add a comment to an AST node
* @param node - Node to add comment to
* @param type - Comment position type
* @param content - Comment text content
* @param line - Whether this is a line comment (default: true)
* @returns The node with added comment
*/
function addComment<T extends t.Node>(
node: T,
type: "leading" | "inner" | "trailing",
content: string,
line?: boolean
): T;Adds multiple comments to a node in the specified position.
/**
* Add multiple comments to an AST node
* @param node - Node to add comments to
* @param type - Comment position type
* @param comments - Array of comment objects
* @returns The node with added comments
*/
function addComments<T extends t.Node>(
node: T,
type: "leading" | "inner" | "trailing",
comments: Array<t.Comment>
): T;Transfers leading comments from a parent node to a child node.
/**
* Inherit leading comments from parent to child node
* @param child - Node to receive comments
* @param parent - Node to copy comments from
*/
function inheritLeadingComments<T extends t.Node>(
child: T,
parent: t.Node
): void;Transfers trailing comments from a parent node to a child node.
/**
* Inherit trailing comments from parent to child node
* @param child - Node to receive comments
* @param parent - Node to copy comments from
*/
function inheritTrailingComments<T extends t.Node>(
child: T,
parent: t.Node
): void;Transfers inner comments from a parent node to a child node.
/**
* Inherit inner comments from parent to child node
* @param child - Node to receive comments
* @param parent - Node to copy comments from
*/
function inheritInnerComments<T extends t.Node>(
child: T,
parent: t.Node
): void;Transfers all comments (leading, trailing, and inner) from parent to child.
/**
* Inherit all comments from parent to child node
* @param child - Node to receive comments
* @param parent - Node to copy comments from
*/
function inheritsComments<T extends t.Node>(
child: T,
parent: t.Node
): void;Removes all comments from a node.
/**
* Remove all comments from an AST node
* @param node - Node to remove comments from
* @returns The node with comments removed
*/
function removeComments<T extends t.Node>(node: T): T;/**
* Comment object structure
*/
interface Comment {
type: "CommentBlock" | "CommentLine";
value: string;
start?: number;
end?: number;
loc?: {
start: { line: number; column: number };
end: { line: number; column: number };
};
}import * as t from "@babel/types";
// Create a function declaration
const funcDecl = t.functionDeclaration(
t.identifier("myFunction"),
[],
t.blockStatement([])
);
// Add leading comment (appears before function)
t.addComment(funcDecl, "leading", " This is an important function", true);
// Add trailing comment (appears after function)
t.addComment(funcDecl, "trailing", " End of function", true);
// Add block comment
t.addComment(funcDecl, "leading", "* This is a block comment *", false);
console.log(funcDecl.leadingComments);
// [
// { type: "CommentLine", value: " This is an important function" },
// { type: "CommentBlock", value: "* This is a block comment *" }
// ]const comments = [
{ type: "CommentLine", value: " First comment" },
{ type: "CommentLine", value: " Second comment" },
{ type: "CommentBlock", value: "* Block comment *" }
] as t.Comment[];
const variable = t.variableDeclaration("const", [
t.variableDeclarator(t.identifier("x"), t.numericLiteral(42))
]);
t.addComments(variable, "leading", comments);
console.log(variable.leadingComments?.length); // 3// Original node with comments
const original = t.identifier("oldName");
t.addComment(original, "leading", " Original variable");
t.addComment(original, "trailing", " End of original");
// New node to replace it
const replacement = t.identifier("newName");
// Inherit all comments
t.inheritsComments(replacement, original);
console.log(replacement.leadingComments); // Comments from original
console.log(replacement.trailingComments); // Comments from originalconst sourceNode = t.binaryExpression(
"+",
t.identifier("a"),
t.identifier("b")
);
t.addComment(sourceNode, "leading", " Start of expression");
t.addComment(sourceNode, "trailing", " End of expression");
t.addComment(sourceNode, "inner", " Inside expression");
const targetNode = t.callExpression(
t.identifier("add"),
[t.identifier("a"), t.identifier("b")]
);
// Inherit only leading comments
t.inheritLeadingComments(targetNode, sourceNode);
console.log(targetNode.leadingComments); // Leading comments only
console.log(targetNode.trailingComments); // undefined
// Inherit trailing comments separately
t.inheritTrailingComments(targetNode, sourceNode);
console.log(targetNode.trailingComments); // Trailing comments now presentconst blockStmt = t.blockStatement([
t.expressionStatement(t.identifier("first")),
t.expressionStatement(t.identifier("second"))
]);
// Inner comments appear inside the block but not attached to specific statements
t.addComment(blockStmt, "inner", " This comment is inside the block");
console.log(blockStmt.innerComments);
// [{ type: "CommentLine", value: " This comment is inside the block" }]
// When transforming, inner comments can be inherited
const newBlock = t.blockStatement([
t.expressionStatement(t.identifier("transformed"))
]);
t.inheritInnerComments(newBlock, blockStmt);
console.log(newBlock.innerComments); // Inherited inner comments// Add JSDoc-style comments
function addJSDocComment(node: t.Node, description: string, params?: string[], returns?: string) {
let jsdoc = `*\n * ${description}\n`;
if (params) {
params.forEach(param => {
jsdoc += ` * @param ${param}\n`;
});
}
if (returns) {
jsdoc += ` * @returns ${returns}\n`;
}
jsdoc += " ";
t.addComment(node, "leading", jsdoc, false);
}
const myFunction = t.functionDeclaration(
t.identifier("calculateSum"),
[t.identifier("a"), t.identifier("b")],
t.blockStatement([
t.returnStatement(
t.binaryExpression("+", t.identifier("a"), t.identifier("b"))
)
])
);
addJSDocComment(
myFunction,
"Calculates the sum of two numbers",
["a - First number", "b - Second number"],
"The sum of a and b"
);import { traverse } from "@babel/types";
// Transform AST while preserving comments
traverse(ast, {
BinaryExpression(path) {
// Transform binary expression to function call
const callExpr = t.callExpression(
t.identifier("operate"),
[
t.stringLiteral(path.node.operator),
path.node.left,
path.node.right
]
);
// Preserve all comments from original node
t.inheritsComments(callExpr, path.node);
path.replaceWith(callExpr);
}
});// Remove all comments from a node tree
function stripComments(node: t.Node): void {
t.traverse(node, {
enter(path) {
t.removeComments(path.node);
}
});
}
// Usage
const nodeWithComments = /* node with many comments */;
stripComments(nodeWithComments);
console.log(nodeWithComments.leadingComments); // undefined// Add comments based on conditions
function addConditionalComments(node: t.Node, debug: boolean, minify: boolean) {
if (debug) {
t.addComment(node, "leading", " DEBUG: Generated node", true);
}
if (!minify) {
t.addComment(node, "trailing", " End of generated code", true);
}
}
// Copy comments selectively
function copyRelevantComments(target: t.Node, source: t.Node, includeInner: boolean) {
t.inheritLeadingComments(target, source);
t.inheritTrailingComments(target, source);
if (includeInner && t.isBlockStatement(target) && t.isBlockStatement(source)) {
t.inheritInnerComments(target, source);
}
}// Generate code templates with embedded comments
function createFunctionTemplate(name: string, withComments: boolean = true) {
const func = t.functionDeclaration(
t.identifier(name),
[t.identifier("args")],
t.blockStatement([
t.expressionStatement(t.identifier("TODO"))
])
);
if (withComments) {
t.addComment(func, "leading", ` TODO: Implement ${name} function`, true);
t.addComment(func.body, "inner", " Function implementation goes here", true);
t.addComment(func, "trailing", ` End of ${name}`, true);
}
return func;
}
const template = createFunctionTemplate("processData");// Analyze comments for specific patterns
function findTodoComments(node: t.Node): string[] {
const todos: string[] = [];
t.traverse(node, {
enter(path) {
const allComments = [
...(path.node.leadingComments || []),
...(path.node.trailingComments || []),
...(path.node.innerComments || [])
];
allComments.forEach(comment => {
if (comment.value.includes("TODO") || comment.value.includes("FIXME")) {
todos.push(comment.value.trim());
}
});
}
});
return todos;
}
// Find all documentation comments
function extractDocComments(node: t.Node): Array<{ node: t.Node; comment: string }> {
const docs: Array<{ node: t.Node; comment: string }> = [];
t.traverse(node, {
enter(path) {
if (path.node.leadingComments) {
path.node.leadingComments.forEach(comment => {
if (comment.type === "CommentBlock" && comment.value.startsWith("*")) {
docs.push({ node: path.node, comment: comment.value });
}
});
}
}
});
return docs;
}