JavaScript CST Implementation
The `cst` package provides a Concrete Syntax Tree (CST) implementation for JavaScript, distinguishing itself from Abstract Syntax Trees (ASTs) by preserving all source code information, including whitespace, comments, and punctuation. This makes it particularly useful for applications requiring precise code transformation, refactoring, linting, and style enforcement, where original formatting must be maintained. The library aims for 100% compatibility with the ESTree AST specification, ensuring that its `Node` structures align with standard AST representations. A core principle is that the tree always remains valid, protecting against structural inconsistencies during modifications. The current stable version is 0.4.10, but its last publish date was over six years ago, indicating that it is in a maintenance state with no active development or new releases. Despite this, its core functionality for detailed source code representation and manipulation remains sound. Key differentiators include its complete representation of the source text, ESTree AST compatibility, and robust mutation methods designed to preserve code integrity and formatting.
Common errors
-
SyntaxError: Unexpected token (X:Y)
cause The input JavaScript string provided to `parse()` contains a syntax error or an unsupported language feature.fixEnsure the input string is valid JavaScript code compatible with the parser's grammar (ES5/ES6+). Check the exact line and column indicated in the error message. -
TypeError: someElement.replaceChildren is not a function
cause Attempting to call `replaceChildren` or similar mutation methods on an `Element` that does not support it (e.g., a `Token` which cannot have children, or an `Element` not yet attached to a tree).fixVerify that `someElement` is a `Node` and is the parent of the `Element`s you intend to replace. Ensure the parent element is part of a valid CST structure. -
TypeError: Cannot read properties of undefined (reading 'getSourceCode')
cause This usually indicates that the `tree` object (or an `Element` within it) is `undefined` or `null`, potentially because `parse()` failed or a traversal path led to an invalid element.fixAdd error handling around `parse()` calls and null checks when traversing the CST, especially after operations that might return `undefined`.
Warnings
- breaking Support for JavaScript Comprehensions (an older, non-standardized feature) was removed. Code relying on parsing or manipulating comprehensions will break.
- gotcha The `Token.cloneElement` method had a bug that was fixed in v0.0.11. If you were relying on specific (potentially incorrect) behavior of cloning tokens in older versions, this fix might subtly change outcomes.
- gotcha CST modification primarily occurs by replacing `Element`s (Nodes or Tokens) within their parent's children. Direct modification of `value` properties on `Token` instances is not supported, as tokens are generally immutable. You must create a new `Token` and replace the old one.
Install
-
npm install cst -
yarn add cst -
pnpm add cst
Imports
- parse
const parse = require('cst').parse;import { parse } from 'cst'; - Node
import Node from 'cst';
import { Node } from 'cst'; - Token
import { CSTToken } from 'cst';import { Token } from 'cst';
Quickstart
import { parse, Token, Node } from 'cst';
const code = `
function greet(name) {
// Say hello to someone
console.log('Hello, ' + name + '!');
}
greet('World'); // We'll change this
`;
// Parse the code into a CST
const tree = parse(code);
console.log('Original Code:\n', tree.getSourceCode());
let worldToken: Token | undefined;
// Find the 'World' string literal token
tree.childElements.forEach(element => {
if (element.isNode && (element as Node).type === 'ExpressionStatement') {
const callExpr = element.childElements.find(c => c.isNode && (c as Node).type === 'CallExpression') as Node;
if (callExpr) {
const literalNode = callExpr.childElements.find(c => c.isNode && (c as Node).type === 'Literal') as Node;
if (literalNode) {
const tokenChild = literalNode.getFirstToken();
if (tokenChild && tokenChild.value === "'World'") {
worldToken = tokenChild;
}
}
}
}
});
if (worldToken) {
const parentOfWorldToken = worldToken.parentElement;
if (parentOfWorldToken) {
// Create a new token for 'CST User'
const newUserToken = new Token('String', "'CST User'");
// Replace the old token within its parent Node
parentOfWorldToken.replaceChildren(newUserToken, worldToken, worldToken);
console.log('\nModified Code (Changed String Literal):\n', tree.getSourceCode());
}
} else {
console.log('\nCould not find "World" token to modify.');
}