I have the following regex:
/(?:this\.(\w+)\(([\s\S]*?)\))/g
it is used to take code like this:
this.doSomething(foo, bar)
and replace it with:
this.lookup('doSomething', [foo, bar])
for that use use case (which is the most common) it works correctly, but it does not work if this
is used within it like this:
this.doSomething(foo, bar, this.baz())
the result incorrectly is this:
this.lookup('doSomething', [foo, bar, this.baz(]))
it should be this:
this.lookup('doSomething', [foo, bar, this.baz()])
Well that's the first problem. It should actually be transformed just like this.doSomething
, so the final result really should be:
this.lookup('doSomething', [foo, bar, this.lookup('baz', [])]);
Basically my regex is assuming the closing parenthesis from this.baz()
is the closing parenthesis of this.doSomething()
and also doesn't operate recursively. I need some sort of recursive behavior/control here.
I've heard of xregexp but I'm not sure how that can help me. It also seems like a true language parser may be the only way to go. I don't have much experience there, but I'm not afraid of getting my hands dirty. It seems tools like Esprima could help?
At the end of the day I'm looking to make minor language/syntax changes in the build step of my code, i.e. exactly like Babel does. I'm in fact using Babel. Maybe some sort of Babel plugin is an option?
Anyway, I'm open to both a quickfix regex trick or more pro/robust language parsing techniques. I'm also just curious how such problems are generally approached. Scanning over the entire input and matching open/closing braces/parentheses/etc I assume??
Here's an example of how you might do this with a Babel plugin:
var names = ['doSomething', 'baz'];
module.exports = function(context){
var t = context.types;
return {
visitor: {
CallExpression: function(path){
var callee = path.get('callee');
// Only process "this.*()" calls.
if (!callee.isMemberExpression() ||
!callee.get('object').isThisExpression() ||
!callee.get('property').isIdentifier()) return;
// Make sure the call is to one of your specific functions.
if (names.indexOf(path.node.callee.property.name) === -1) return;
// Build "this.lookup('<name>', [])".
path.replaceWith(t.callExpression(
t.memberExpression(t.thisExpression(), t.identifier('lookup')),
[
t.stringLiteral(path.node.callee.property.name),
t.arrayExpression(path.node.arguments),
]
));
}
}
};
}
If you drop that into a plugin.js
function for instance, you can create a .babelrc
config file and make sure ./plugin.js
or whatever path points to it, is in your plugins
array, e.g.
{
"presets": ['es2015'],
"plugins": ['./plugin']
}