JavaScript CST Implementation

0.4.10 · maintenance · verified Sun Apr 19

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

Warnings

Install

Imports

Quickstart

Demonstrates parsing JavaScript code into a CST, traversing the tree to find a specific string literal token, and then modifying that token's value while preserving surrounding whitespace and comments.

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.');
}

view raw JSON →