130 lines
3.1 KiB
JavaScript
130 lines
3.1 KiB
JavaScript
|
|
/**
|
|
* 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;
|
|
}
|