Abstract Syntax Trees AST in JavaScript Guide
An Abstract Syntax Tree is the structured representation of your JavaScript code after parsing. Learn what ASTs are, how tools like Babel and ESLint use them, and how to explore ASTs yourself.
An Abstract Syntax Tree, or AST, is the data structure a JavaScript engine builds after reading your source code and before running it. Think of it as a structured map of every keyword, operator, variable name, and expression in your program, organized as a tree of nodes.
Every JavaScript tool that understands your code, from the V8 engine to Prettier, works with an AST. The engine uses it to generate bytecode.
A linter walks it to find problems. A formatter rewrites it and prints the result back as clean code.
How Source Code Becomes an AST
Before any JavaScript runs, the engine must parse it. Parsing happens in two stages:
The lexer scans your code character by character and groups them into tokens: keywords, operators, identifiers, and literals. The parser takes that flat list of tokens and builds a nested tree that captures the grammatical structure of your program.
Here is a tiny example. Given this line:
const sum = a + 1;The parser turns that single line into a tree of typed objects, one per keyword, operator, and identifier. Each object records its own kind and points to the objects nested inside it, so the whole statement becomes a small, walkable structure instead of a flat string. Here is that structure written out as JSON:
{
"type": "VariableDeclaration",
"kind": "const",
"declarations": [{
"type": "VariableDeclarator",
"id": { "type": "Identifier", "name": "sum" },
"init": {
"type": "BinaryExpression",
"operator": "+",
"left": { "type": "Identifier", "name": "a" },
"right": { "type": "Literal", "value": 1 }
}
}]
}The tree says: this is a const declaration named sum, whose value is a binary expression adding the identifier a and the literal 1. Every piece of meaning is preserved as a typed node with a known position in the tree.
Why ASTs Matter
You may never write an AST by hand, but you depend on them constantly:
| Tool | What it does with the AST |
|---|---|
| Babel | Reads your modern JS, walks the AST, transforms nodes (like arrow functions into regular functions), and prints ES5 code |
| ESLint | Parses your code, walks the AST, and checks each node against rules |
| Prettier | Parses your code into an AST, then prints it back with consistent formatting |
| TypeScript | Builds a typed AST, checks type constraints, then emits plain JavaScript |
| Minifiers (Terser) | Walk the AST and shorten identifiers, remove dead branches, and collapse expressions |
| V8 / SpiderMonkey | Build an AST, then compile it to bytecode or machine code |
Every time you save a file and your editor auto-formats it, an AST was built, inspected, and reprinted. Every time you run a linter, every rule walks the AST looking for patterns. The AST is the universal interface between your source text and any tool that reasons about it.
Exploring an AST with AST Explorer
The fastest way to understand ASTs is to see one. Open AST Explorer, paste a line of JavaScript in the left pane, and watch the tree appear on the right. Try pasting this:
function greet(name) {
return `Hello, ${name}!`;
}You will see a root program node, containing a function declaration. That declaration has a name, a list of parameters, and a body block holding the return statement. Click any node to see its source location highlighted in the original code.
AST Explorer supports multiple parsers. The three you will see most often are:
| Parser | Used by |
|---|---|
| espree | ESLint |
| @babel/parser | Babel |
| acorn | Webpack, Rollup, many bundlers |
The node types are similar but not identical. Babel's AST adds extra metadata for source maps and comments. ESLint's AST is stricter and follows the ESTree specification.
AST Node Types Every Developer Should Recognize
While the full ESTree specification lists dozens of node types, a few appear in nearly every program:
| Node Type | What it represents | Example source |
|---|---|---|
Program | The root of every file | the whole script |
VariableDeclaration | const, let, or var statement | const x = 1; |
FunctionDeclaration | A named function | function foo() {} |
ArrowFunctionExpression | An arrow function | (x) => x + 1 |
BinaryExpression | An operation with two operands | a + b |
CallExpression | A function call | console.log(1) |
MemberExpression | Property access | obj.prop or obj["prop"] |
IfStatement | An if/else block | if (x) {} |
Literal | A raw value | 42, "hello", true |
Identifier | A variable or function name | myVar |
Each node has a type property and a set of children that reference other nodes.
A binary expression node has a left side, an operator, and a right side. A call expression node has a callee and a list of arguments. The tree is recursive: every child is itself a valid AST node.
How Babel Transforms Code Using an AST
Babel is the most widely used AST manipulation tool in the JavaScript ecosystem. It works in three phases:
A Babel plugin is a plain object with a visitor property. Babel calls the matching function on the visitor every time it walks past a node of that type, and the function can read or rewrite that node in place. Here is a minimal plugin that turns every const into let:
// A toy Babel plugin: replace every const with let
module.exports = function () {
return {
visitor: {
VariableDeclaration(path) {
if (path.node.kind === "const") {
path.node.kind = "let";
}
}
}
};
};Babel calls the VariableDeclaration function every time it walks past a node of that type. The path object wraps the current node and provides methods for replacing, removing, or inserting sibling nodes. The plugin above simply flips kind from const to let.
Real plugins are more involved. The arrow function transform plugin, for instance, replaces an arrow function node with a regular function expression, moves the this binding, and adjusts the body.
The pattern stays the same across every plugin: visit a node, read its properties, and replace it with new nodes.
How ESLint Uses the AST to Find Problems
ESLint rules follow the same visitor pattern as Babel plugins, but instead of transforming nodes they report problems:
// A toy ESLint rule: forbid var declarations
module.exports = {
meta: {
type: "suggestion",
docs: { description: "disallow var declarations" }
},
create(context) {
return {
VariableDeclaration(node) {
if (node.kind === "var") {
context.report({
node,
message: "Unexpected var, use const or let instead."
});
}
}
};
}
};The structure is identical: select a node type, inspect its properties, and react. ESLint reports a message with a severity instead of mutating the tree.
This is the power of ASTs. Transformers, linters, and formatters all use the same tree structure. Once you can read an AST, you can write any of these tools.
Practical Use Cases for Custom AST Tools
You do not need to write a full Babel plugin to benefit from ASTs. Smaller practical uses include:
- Codemods: Scripts that automate large-scale refactors across hundreds of files. A codemod to rename a deprecated API or restructure imports is a Babel plugin run in batch mode.
- Custom lint rules: Enforce project-specific conventions that no public ESLint plugin covers, like "all API calls must include an error boundary" or "use a specific import order."
- Code generation: Read a schema, configuration, or API definition and output JavaScript. Tools like GraphQL Code Generator build an AST and print it, ensuring valid syntax.
- Documentation extractors: Walk the AST to find exported functions and their JSDoc comments, then generate markdown.
- Dead code detection: Walk the AST to find unreachable branches, unused variables, or imports that are never referenced, then remove or report them.
The common thread is: you need to understand your own code programmatically. An AST gives you a precise, queryable model of that code.
Exploring Further
The best next step is hands-on: paste some code into AST Explorer, study the tree, and try writing a one-rule Babel plugin that changes a single node type. The mental model transfers to every AST-based tool you will ever use.
Related reading:
- What is a JavaScript engine: see where parsing fits in the bigger picture of running your code.
- How the V8 engine compiles JavaScript: learn what happens to the AST after it is built.
Rune AI
Key Insights
- An AST is a tree-shaped data structure that represents the syntax of your source code.
- The JavaScript engine builds an AST before compiling or interpreting your code.
- Tools like Babel, ESLint, and Prettier read, analyze, and rewrite ASTs.
- Use AST Explorer to paste code and see its tree structure in real time.
- Writing a Babel plugin or ESLint rule means walking and modifying an AST.
Frequently Asked Questions
Do I need to understand ASTs to write JavaScript?
Are all ASTs the same across different parsers?
Conclusion
An AST is the bridge between the text you write and the instructions the engine runs. Every time you format, lint, or transform JavaScript code, an AST is built, inspected, and sometimes rewritten. You do not need to master ASTs to be a productive developer, but peeking under the hood reveals how your tools really work.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.