npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

flast

v2.2.5

Published

Flatten JS AST

Readme

flAST - FLat Abstract Syntax Tree

Run Tests Downloads

Efficient, flat, and richly-annotated JavaScript AST manipulation for code transformation, analysis, and more.

For comments and suggestions feel free to open an issue or find me on Twitter/X - @ctrl__esc

Table of Contents


Installation

Requires Node 18 or newer.

npm

npm install flast

Clone the Repo

git clone [email protected]:PerimeterX/flast.git
cd flast
npm install

Features

Parsing and Code Generation

  • Code to AST: Parse JavaScript code into a flat, richly annotated AST.
  • AST to Code: Generate code from any AST node, supporting round-trip transformations.

Flat AST Structure

  • Flat AST (generateFlatAST): All nodes are in a single array, allowing direct access and efficient traversal without recursive tree-walking.
  • Type Map: ast[0].typeMap provides fast lookup of all nodes by type.
  • Scopes: ast[0].allScopes gives direct access to all lexical scopes.

Node Richness

Each node in the flat AST includes:

  • src: The original code for this node.
  • parentNode and childNodes: Easy navigation and context.
  • parentKey: The property name this node occupies in its parent.
  • declNode: For variables, a reference to their declaration node.
  • references: For declarations, a list of all reference nodes.
  • lineage: Traceable ancestry of scopes for each node.
  • nodeId: Unique identifier for each node.
  • scope, scopeId, and more for advanced analysis.

Arborist: AST Modification

  • Delete nodes: Mark nodes for removal and apply changes safely.
  • Replace nodes: Mark nodes for replacement, with all changes validated and applied in a single pass.

Utilities

  • applyIteratively: Apply a series of transformation functions (using Arborist) to the AST/code, iterating until no further changes are made. Automatically reverts changes that break the code.
  • logger: Simple log utility that can be controlled downstream and used for debugging or custom output.
  • treeModifier: (Deprecated) Simple wrapper for AST iteration.

Usage Examples

Tip: For best performance, always iterate over only the relevant node types using ast[0].typeMap. For example, to process all identifiers and variable declarations:

const relevantNodes = [
  ...ast[0].typeMap.Identifier, 
  ...ast[0].typeMap.VariableDeclarator,
];
for (let i = 0; i < relevantNodes.length; i++) {
  const n = relevantNodes[i];
  // ... process n ...
}

Only iterate over the entire AST as a last resort.

Basic Example

import {Arborist} from 'flast';

const replacements = {'Hello': 'General', 'there!': 'Kenobi'};

const arb = new Arborist(`console.log('Hello' + ' ' + 'there!');`);
// This is equivalent to:
//   const ast = generateFlatAST(`console.log('Hello' + ' ' + 'there!');`);
//   const arb = new Arborist(ast);
// Since the Arborist accepts either code as a string or a flat AST object.

for (let i = 0; i < arb.ast.length; i++) {
  const n = arb.ast[i];
  if (n.type === 'Literal' && replacements[n.value]) {
    arb.markNode(n, {
      type: 'Literal',
      value: replacements[n.value],
      raw: `'${replacements[n.value]}'`,
    });
  }
}
arb.applyChanges();
console.log(arb.script); // console.log('General' + ' ' + 'Kenobi');

Example 1: Numeric Calculation Simplification

Replace constant numeric expressions with their computed value.

import {applyIteratively} from 'flast';

function simplifyNumericExpressions(arb) {
  const binaryNodes = arb.ast[0].typeMap.BinaryExpression || [];
  for (let i = 0; i < binaryNodes.length; i++) {
    const n = binaryNodes[i];
    if (n.left.type === 'Literal' && typeof n.left.value === 'number' &&
      n.right.type === 'Literal' && typeof n.right.value === 'number') {
      let result;
      switch (n.operator) {
        case '+': result = n.left.value + n.right.value; break;
        case '-': result = n.left.value - n.right.value; break;
        case '*': result = n.left.value * n.right.value; break;
        case '/': result = n.left.value / n.right.value; break;
        default: continue;
      }
      arb.markNode(n, {type: 'Literal', value: result, raw: String(result)});
    }
  }
  return arb;
}

const script = 'let x = 5 * 3 + 1;';
const result = applyIteratively(script, [simplifyNumericExpressions]);
console.log(result); // let x = 16;

Example 2: Transform Arrow Function to Regular Function

import {applyIteratively} from 'flast';

function arrowToFunction(arb) {
  const arrowNodes = arb.ast[0].typeMap.ArrowFunctionExpression || [];
  for (let i = 0; i < arrowNodes.length; i++) {
    const n = arrowNodes[i];
    arb.markNode(n, {
      type: 'FunctionExpression',
      id: null,
      params: n.params,
      body: n.body.type === 'BlockStatement' ? n.body : {type: 'BlockStatement', body: [{ type: 'ReturnStatement', argument: n.body }] },
      generator: false,
      async: n.async,
      expression: false,
    });
  }
  return arb;
}

const script = 'const f = (a, b) => a + b;';
const result = applyIteratively(script, [arrowToFunction]);
console.log(result); 
/* 
const f = function(a, b) {
  return a + b;
};
*/

Example 3: Modify Nodes Based on Attached Comments

Suppose you want to double any numeric literal that has a comment // double attached:

import {applyIteratively} from 'flast';

function doubleLiteralsWithComment(arb) {
  const literalNodes = arb.ast[0].typeMap.Literal || [];
  for (let i = 0; i < literalNodes.length; i++) {
    const n = literalNodes[i];
    if (
      typeof n.value === 'number' &&
      n.leadingComments &&
      n.leadingComments.some(c => c.value.includes('double'))
    ) {
      arb.markNode(n, { type: 'Literal', value: n.value * 2, raw: String(n.value * 2) });
    }
  }
  return arb;
}

const script = 'const x = /* double */ 21;';
const result = applyIteratively(script, [doubleLiteralsWithComment], 1); // Last argument is the maximum number of iterations allowed.
console.log(result); // const x = /* double */ 42;

Example 4: Proxy Variable Replacement Using References

Replace all references to a variable that is a proxy for another variable.

import {applyIteratively} from 'flast';

function replaceProxyVars(arb) {
  const declarators = arb.ast[0].typeMap.VariableDeclarator || [];
  for (let i = 0; i < declarators.length; i++) {
    const n = declarators[i];
    if (n.init && n.init.type === 'Identifier' && n.id && n.id.name) {
      // Replace all references to this variable with the variable it proxies
      const refs = n.references || [];
      for (let j = 0; j < refs.length; j++) {
        const ref = refs[j];
        arb.markNode(ref, {
          type: 'Identifier',
          name: n.init.name,
        });
      }
    }
  }
  return arb;
}

const script = 'var a = b; var b = 42; console.log(a);';
const result = applyIteratively(script, [replaceProxyVars]);
console.log(result); // var a = b; var b = 42; console.log(b);

Projects Using flAST

  • Obfuscation-Detector: Uses flAST to analyze and detect unique obfuscation in JavaScript code.
  • REstringer: Uses flAST for advanced code transformation and analysis.

Best Practices

AST Mutability

You can directly mutate nodes in the flat AST (e.g., changing properties, adding or removing nodes). However, for safety and script validity, it's best to use the Arborist for all structural changes. The Arborist verifies your changes and prevents breaking the code, ensuring that the resulting AST remains valid and that all node information is updated correctly.

  • Direct mutation is possible, but should be used with caution.
  • Recommended: Use the Arborist's markNode method for all node deletions and replacements.

How to Contribute

To contribute to this project see our contribution guide