blob: ce76662c688c9759ca596854347dd1e459708f80 [file] [log] [blame]
/**
* Module dependencies.
*/
var types = require('ast-types');
var esprima = require('esprima');
var escodegen = require('escodegen');
/**
* Helper functions.
*/
var n = types.namedTypes;
var b = types.builders;
/**
* Module exports.
*/
module.exports = degenerator;
/**
* Turns sync JavaScript code into an JavaScript with async Generator Functions.
*
* @param {String} jsStr JavaScript string to convert
* @param {Array} names Array of function names to add `yield` operators to
* @return {String} Converted JavaScript string with Generator functions injected
* @api public
*/
function degenerator (jsStr, names) {
if (!Array.isArray(names)) {
throw new TypeError('an array of async function "names" is required');
}
var ast = esprima.parse(jsStr);
// duplicate the `names` array since it's rude to augment the user-provided
// array
names = names.slice(0);
// first pass is to find the `function` nodes and turn them into `function *`
// generator functions. We also add the names of the functions to the `names`
// array
types.visit(ast, {
visitFunction: function(path) {
if (path.node.id) {
// got a "function" expression/statement,
// convert it into a "generator function"
path.node.generator = true;
// add function name to `names` array
names.push(path.node.id.name);
}
this.traverse(path);
}
});
// second pass is for adding `yield` statements to any function
// invocations that match the given `names` array.
types.visit(ast, {
visitCallExpression: function(path) {
if (checkNames(path.node, names)) {
// a "function invocation" expression,
// we need to inject a `YieldExpression`
var name = path.name;
var parent = path.parent.node;
var delegate = false;
var expr = b.yieldExpression(path.node, delegate);
if (parent['arguments']) {
// parent is a `CallExpression` type
parent['arguments'][name] = expr;
} else {
parent[name] = expr;
}
}
this.traverse(path);
}
});
return escodegen.generate(ast);
}
/**
* Returns `true` if `node` has a matching name to one of the entries in the
* `names` array.
*
* @param {types.Node} node
* @param {Array} names Array of function names to return true for
* @return {Boolean}
* @api private
*/
function checkNames (node, names) {
var name;
var callee = node.callee;
if ('Identifier' == callee.type) {
name = callee.name;
} else if ('MemberExpression' == callee.type) {
name = callee.object.name + '.' + (callee.property.name || callee.property.raw);
} else if ('FunctionExpression' == callee.type) {
if (callee.id) {
name = callee.id.name;
} else {
return false;
}
} else {
throw new Error('don\'t know how to get name for: ' + callee.type);
}
// now that we have the `name`, check if any entries match in the `names` array
var n;
for (var i = 0; i < names.length; i++) {
n = names[i];
if (n.test) {
// regexp
if (n.test(name)) return true;
} else {
if (name == n) return true;
}
}
return false;
}