Mastering JavaScript AST: From Basics to Real-World Code Transformations

This article introduces the concept of Abstract Syntax Trees (AST) in JavaScript, explains their purpose and generation process, and demonstrates practical examples such as removing debugger statements and enhancing console.log calls using Babel’s parser, traverse, and generator tools.

WeDoctor Frontend Technology
WeDoctor Frontend Technology
WeDoctor Frontend Technology
Mastering JavaScript AST: From Basics to Real-World Code Transformations

Introduction

When writing business code we rarely use an AST, so most developers are unfamiliar with it. This article teaches the fundamentals of Abstract Syntax Trees, their uses, how they are generated, and provides hands‑on examples.

Basic Knowledge

What is an AST?

In computer science, an abstract syntax tree (AST) is a tree representation of the abstract syntactic structure of source code, specifically for programming languages.

For example, the JavaScript source var tree = 'this is tree' is transformed into the following AST:

{
  "type": "Program",
  "start": 0,
  "end": 25,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 25,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 4,
          "end": 25,
          "id": {
            "type": "Identifier",
            "start": 4,
            "end": 8,
            "name": "tree"
          },
          "init": {
            "type": "Literal",
            "start": 11,
            "end": 25,
            "value": "this is tree",
            "raw": "'this is tree'"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "module"
}

What can an AST be used for?

IDE error hints, code formatting, syntax highlighting, auto‑completion

JSLint / JSHint code style checks

Webpack, Rollup bundling

Transpiling CoffeeScript, TypeScript, JSX to native JavaScript

Vue and React template compilation

How is an AST generated?

The parsing process consists of two steps:

Lexical analysis: scans the source string and produces a list of tokens (numbers, punctuation, operators, etc.).

Syntax analysis: builds relationships between tokens to produce the AST.

Using the same example var tree = 'this is tree', lexical analysis yields tokens, and syntax analysis assembles them into the AST shown above.

Formal Understanding

Lexical Analysis

Scans the source code and generates tokens.

Syntax Analysis

Converts the token list into an AST (fields like start and end are omitted for clarity).

Informal Understanding

Disclaimer: My Chinese is barely passing; just grasp the gist.

Example: "It is a pig."

Lexical Analysis

Same token generation process, illustrated with Chinese characters.

Syntax Analysis

Tokens are assembled into an AST, shown in the image below.

JavaScript Parsers

acorn

esprima

traceur

@babel/parser

Practical Example 1: Remove debugger

function fn() {
  console.log('debugger')
  debugger;
}

Steps:

Parse the source into an AST.

Traverse the AST, locate DebuggerStatement nodes, and delete them.

Generate JavaScript code from the transformed AST.

const parser = require('@babel/parser');
const traverse = require("@babel/traverse");
const generator = require("@babel/generator");

// source code
const code = `
function fn() {
  console.log('debugger')
  debugger;
}
`;

// 1. parse to AST
const ast = parser.parse(code);

// 2. transform
const visitor = {
  DebuggerStatement(path) {
    path.remove();
  }
};
traverse.default(ast, visitor);

// 3. generate code
const result = generator.default(ast, {}, code);
console.log(result.code);

The core logic resides in the visitor where path.remove() deletes the debugger node.

Practical Example 2: Enhance console.log Arguments

Original code:

function funA() {
  console.log(1)
}

Desired transformation adds a prefix indicating the function name:

function funA() {
  console.log('from function funA:', 1)
}

Steps:

Parse source to AST with @babel/parser.

Traverse AST, detect CallExpression where callee is console.log.

Insert a StringLiteral at the beginning of arguments using unshift, containing the function name.

Generate the transformed code.

const visitor = {
  CallExpression(path) {
    const callee = path.node.callee;
    if (types.isMemberExpression(callee)) {
      const { object, property } = callee;
      if (types.isIdentifier(object, { name: 'console' }) && types.isIdentifier(property, { name: 'log' })) {
        const parent = path.getFunctionParent();
        const parentFunName = parent.node.id.name;
        path.node.arguments.unshift(types.stringLiteral(`from function ${parentFunName}:`));
      }
    }
  }
};

Conclusion

Although we seldom manipulate ASTs in daily work, understanding them helps us grasp the inner workings of development tools and compilers, enabling us to build utilities that improve code efficiency. For example, an AST‑based ESLint plugin was created to validate client‑only code in an SSR project.

References

babel‑handbook (https://github.com/jamiebuilds/babel-handbook)

深入 Babel (https://juejin.im/post/6844903746804137991)

高级前端基础‑JavaScript 抽象语法树 AST (https://juejin.cn/post/6844903798347939853#heading-12)

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ASTbabelcode transformation
WeDoctor Frontend Technology
Written by

WeDoctor Frontend Technology

Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.