To write a simple Babel plugin, we can use http://astexplorer.net/ to help us.
The plugin we want to write is:
var foo = 'o' var bar = 'o' foo === bar function foo(foo, bar) { foo === bar; }
We want to trasnform the code which highlighted in foo() function to:
_foo === _bar
Notice that, we also have 'foo === bar' which is not inside foo() function. But we don't want to touch that.
To get started, we have code below:
export default function(babel) { const { types: t } = babel; return { name: "add_underscore", visitor: { // your code here } }; }
All the code is under 'visitor' prop.
By hightlight the code we can see, it is a 'BinaryExpression':
So we focus on 'BinaryExpression':
export default function(babel) { const { types: t } = babel; return { name: "add_underscore", visitor: { BinaryExpression(path) { } } }; }
'path' param contains all the information we need.
If add a console.log() inside BinarayExpression() function, we can see two logs, one is from global scope, another one is from 'foo()' scope. We only want to get one from foo() function scope:
The way to do is check 'path.scope.block.type', which is current code' block scope.
'foo === bar' exisits on global belongs to 'Program':
the one exists in foo() belongs to 'BlockStatement':
BinaryExpression(path) {
// remove global one if (path.scope.block.type === "Program") { return; } }
And the opreator we want to check is '===':
BinaryExpression(path) { if (path.scope.block.type === "Program") { return; } if (path.node.operator !== "===") { return; }
}
Now we have located the one 'foo === bar' we want, we can start to replace the name:
export default function(babel) { const { types: t } = babel; return { name: "add_underscore", visitor: { BinaryExpression(path) { if (path.scope.block.type === "Program") { return; } if (path.node.operator !== "===") { return; } // locate the 'foo' and 'bar' // as left and right Identifier const leftIdentifier = path.node.left; const rightIndentifier = path.node.right; // generate a new identifier const newLeftIdentifier = path.scope.generateUidIdentifier(leftIdentifier.name); const newRightIdentifier = path.scope.generateUidIdentifier( rightIndentifier.name ); // replace the old with new one path.node.left = t.identifier(newLeftIdentifier.name); path.node.right = t.identifier(newRightIdentifier.name); } } }; }
Now the generate code looks like:
var foo = 'o' var bar = 'o' foo === bar function foo(foo, bar) { _foo === _bar; }
The code have successfully transform to '_foo === _bar'.
But clearly the code won't work, because _foo and _bar is undefined.
We need to update the params in the function as well.
// update params in the function const [fooParam, barParam] = path.scope.block.params; fooParam.name = t.identifier(newLeftIdentifier.name).name; barParam.name = t.identifier(newRightIdentifier.name).name;
All Code:
export default function(babel) { const { types: t } = babel; return { name: "add_underscore", visitor: { BinaryExpression(path) { if (path.scope.block.type === "Program") { return; } if (path.node.operator !== "===") { return; } // locate the 'foo' and 'bar' // as left and right Identifier const leftIdentifier = path.node.left; const rightIndentifier = path.node.right; // generate a new identifier const newLeftIdentifier = path.scope.generateUidIdentifier(leftIdentifier.name); const newRightIdentifier = path.scope.generateUidIdentifier( rightIndentifier.name ); // replace the old with new one path.node.left = t.identifier(newLeftIdentifier.name); path.node.right = t.identifier(newRightIdentifier.name); // update params in the function const [fooParam, barParam] = path.scope.block.params; fooParam.name = t.identifier(newLeftIdentifier.name).name; barParam.name = t.identifier(newRightIdentifier.name).name; } } }; }
[Notice]: this is a just learning note for myself. The approache might not be optimal.