How to Harness AST for JavaScript, JSX, and ESLint Transformations
This article explains what an Abstract Syntax Tree (AST) is, outlines its parse‑transform‑generate workflow, and demonstrates practical AST‑based code transformations using Babel, custom ESLint rules, and a JSX‑to‑object Babel plugin, complete with example code and visual illustrations.
What is AST?
AST (Abstract Syntax Tree) is a tree representation of source code that enables tools such as JavaScript transpilers, CSS preprocessors, ESLint, Prettier, and others to analyze and transform code without executing it.
AST Workflow
The typical workflow consists of three steps: parse – converting source code into an AST; transform – visiting and modifying AST nodes (the bulk of business logic); and generator – turning the transformed AST back into source code.
Simple Requirement Example
Goal: rename the parameter num to n in a square function and update all references, while leaving unrelated strings untouched.
Solution 1: Direct replace (unsafe)
const sourceText = `function square(num) {
return num * num;
}`;
sourceText.replace(/num/g, 'n');This naïve approach also changes occurrences inside strings and other identifiers, leading to bugs.
// before
function square(num) {
return num * num;
}
console.log('param 2 result num is ' + square(2));
// after
function square(n) {
return n * n;
}
console.log('param 2 result n is ' + square(2));Solution 2: Using Babel for AST transformation
module.exports = () => {
return {
visitor: {
Identifier(path) {
if (path.node.name === 'num') {
path.node.name = 'n';
}
}
}
};
};This visitor changes every identifier named num, but also rewrites window.num, causing errors.
// before
function square(num) {
return num * num;
}
console.log('global num is ' + window.num);
// after (incorrect)
function square(n) {
return n * n;
}
console.log('global num is ' + window.n); // errorSolution 2 Upgrade: Precise reference handling
module.exports = () => {
return {
visitor: {
Identifier(path) {
if (path.node.name !== 'num') return;
if (path.parent.type !== 'FunctionDeclaration') return;
if (path.parent.id.name !== 'square') return;
const referencePaths = path.scope.bindings['num'].referencePaths;
referencePaths.forEach(p => p.node.name = 'n');
path.node.name = 'n';
}
}
};
};This version limits the rename to the square function’s parameter and its references, leaving other num usages untouched.
Babel in AST
Babel provides three core packages for AST work: @babel/parser – parses source code into an AST. @babel/traverse – walks the AST and runs visitor callbacks. @babel/generator – generates source code from an AST.
Additional helpers include @babel/types for node creation and checks, and @babel/template for building nodes from code snippets.
const parser = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');
const template = require('@babel/template').default;ESLint in AST
Custom ESLint rules can be written by visiting AST nodes. Example rule that warns when a variable name is a single character:
module.exports.rules = {
"var-length": context => ({
VariableDeclarator(node) {
if (node.id.name.length <= 1) {
context.report(node, 'Variable name length must be greater than 1');
}
}
})
};Configuration in .eslintrc.js enables the rule.
module.exports = {
root: true,
parserOptions: { ecmaVersion: 6 },
plugins: ["my-eslint-plugin"],
rules: { "my-eslint-plugin/var-length": "warn" }
};Getting JSX Interpretation Rights
JSX is a JavaScript syntax extension used by React but not limited to it. By writing a Babel plugin we can transform JSX into a generic object representation that can be consumed by different runtimes (Web, mini‑programs, etc.).
// JSX source
<view visible onTap={e => console.log('clicked')}>ABC<button>login</button></view>Desired output:
{
type: 'view',
visible: true,
children: [
'ABC',
{ type: 'button', children: ['login'] }
]
}JSX Babel Plugin
const { declare } = require('@babel/helper-plugin-utils');
const jsx = require('@babel/plugin-syntax-jsx').default;
const core = require('@babel/core');
const t = core.types;
const handleJSXElement = node => {
const tag = node.openingElement;
const type = tag.name.name;
const properties = [t.objectProperty(t.identifier('type'), t.stringLiteral(type))];
const attributes = tag.attributes || [];
attributes.forEach(attr => {
if (attr.type === 'JSXAttribute') {
const key = t.identifier(attr.name.name);
const convert = n => {
if (t.isJSXExpressionContainer(n)) return n.expression;
if (n === null) return t.booleanLiteral(true);
return n;
};
const value = convert(attr.value);
properties.push(t.objectProperty(key, value));
}
});
const children = node.children.map(e => {
if (e.type === 'JSXElement') return handleJSXElement(e);
if (e.type === 'JSXText') return t.stringLiteral(e.value);
return e;
});
properties.push(t.objectProperty(t.identifier('children'), t.arrayExpression(children)));
return t.objectExpression(properties);
};
module.exports = declare((api, options) => ({
inherits: jsx,
visitor: {
JSXElement(path) {
path.replaceWith(handleJSXElement(path.node));
}
}
}));Conclusion
We introduced what AST is, its workflow, and demonstrated powerful transformations using Babel, ESLint, and custom JSX plugins. AST can serve as a strong weapon for code automation, visual programming, and custom linting scenarios.
All example code and tests are available at https://github.com/chvin/learn_ast .
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
