| |
| /** |
| * 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; |
| } |