AST Fundamentals
How ESLint reads and analyzes your code
The Tree Inside Your Code
ESLint doesn't read code like humans. It parses your JavaScript into an Abstract Syntax Tree (AST)—a structured representation of your code's grammar.
Understanding the AST unlocks:
- Custom rule creation for your specific patterns
- Better debugging of false positives
- Advanced selectors for precise rule targeting
What is an AST?
When you write:
const query = 'SELECT * FROM users WHERE id = ' + userId;ESLint parses it into a tree structure:
Program
└── VariableDeclaration (kind: "const")
└── VariableDeclarator
├── Identifier (name: "query")
└── BinaryExpression (operator: "+")
├── Literal (value: "SELECT * FROM users WHERE id = ")
└── Identifier (name: "userId")Each node in the tree represents a syntactic element:
Program— The root of every JavaScript fileVariableDeclaration— Aconst,let, orvarstatementBinaryExpression— An operation like+,-,*, etc.Identifier— A variable nameLiteral— A raw value like a string or number
How Security Rules Use the AST
Security rules match patterns in the AST. For example, the no-sql-concatenation rule triggers when it finds:
BinaryExpression (operator: "+")
├── Literal (value contains SQL keywords)
└── Identifier (untrusted input)This pattern-matching approach is:
- Precise — Only matches actual vulnerable patterns
- Fast — Tree traversal is O(n) complexity
- Flexible — Can target any syntactic structure
ESLint Selectors
ESLint uses CSS-like selectors to target AST nodes. Here are common patterns:
| Selector | Matches |
|---|---|
CallExpression | Any function call |
CallExpression[callee.name="eval"] | Calls to eval() |
BinaryExpression[operator="+"] | Any + operation |
VariableDeclaration[kind="var"] | var declarations |
Identifier[name="password"] | Variables named "password" |
Advanced Selector Example
To match dangerous SQL concatenation:
// This selector:
'BinaryExpression[operator="+"] > Literal[value=/SELECT|INSERT|UPDATE|DELETE/i]';
// Matches patterns like:
const query = 'SELECT * FROM users' + userInput;Common AST Node Types
Declarations
| Node Type | Example |
|---|---|
VariableDeclaration | const x = 1 |
FunctionDeclaration | function foo() {} |
ClassDeclaration | class Foo {} |
Expressions
| Node Type | Example |
|---|---|
CallExpression | foo() |
MemberExpression | obj.prop |
BinaryExpression | a + b |
TemplateLiteral | `Hello ${name}` |
Statements
| Node Type | Example |
|---|---|
IfStatement | if (x) {} |
ReturnStatement | return value |
ThrowStatement | throw error |
Try It Yourself
Explore AST Interactively
The best way to understand AST is to experiment with real code. Use the tools below to visualize how ESLint parses your JavaScript.
Recommended tools:
- AST Explorer — The gold standard for AST visualization. Select
@babel/eslint-parserto see exactly what ESLint sees. - ESLint Playground — Test rules against code in real-time
Writing Custom Rules
Understanding the AST enables you to write custom rules. Here's a minimal example:
module.exports = {
meta: {
type: 'suggestion',
docs: { description: 'Disallow console.log' },
},
create(context) {
return {
// Selector: CallExpression where callee is console.log
'CallExpression[callee.object.name="console"][callee.property.name="log"]'(
node,
) {
context.report({
node,
message: 'Unexpected console.log statement',
});
},
};
},
};