"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.degenerator = void 0; const util_1 = require("util"); const escodegen_1 = require("escodegen"); const esprima_1 = require("esprima"); const ast_types_1 = require("ast-types"); /** * Compiles sync JavaScript code into JavaScript with async Functions. * * @param {String} code JavaScript string to convert * @param {Array} names Array of function names to add `await` operators to * @return {String} Converted JavaScript string with async/await injected * @api public */ function degenerator(code, _names) { if (!Array.isArray(_names)) { throw new TypeError('an array of async function "names" is required'); } // Duplicate the `names` array since it's rude to augment the user args const names = _names.slice(0); const ast = (0, esprima_1.parseScript)(code); // First pass is to find the `function` nodes and turn them into async or // generator functions only if their body includes `CallExpressions` to // function in `names`. We also add the names of the functions to the `names` // array. We'll iterate several time, as every iteration might add new items // to the `names` array, until no new names were added in the iteration. let lastNamesLength = 0; do { lastNamesLength = names.length; (0, ast_types_1.visit)(ast, { visitVariableDeclaration(path) { if (path.node.declarations) { for (let i = 0; i < path.node.declarations.length; i++) { const declaration = path.node.declarations[i]; if (ast_types_1.namedTypes.VariableDeclarator.check(declaration) && ast_types_1.namedTypes.Identifier.check(declaration.init) && ast_types_1.namedTypes.Identifier.check(declaration.id) && checkName(declaration.init.name, names) && !checkName(declaration.id.name, names)) { names.push(declaration.id.name); } } } return false; }, visitAssignmentExpression(path) { if (ast_types_1.namedTypes.Identifier.check(path.node.left) && ast_types_1.namedTypes.Identifier.check(path.node.right) && checkName(path.node.right.name, names) && !checkName(path.node.left.name, names)) { names.push(path.node.left.name); } return false; }, visitFunction(path) { if (path.node.id) { let shouldDegenerate = false; (0, ast_types_1.visit)(path.node, { visitCallExpression(path) { if (checkNames(path.node, names)) { shouldDegenerate = true; } return false; }, }); if (!shouldDegenerate) { return false; } // Got a "function" expression/statement, // convert it into an async function path.node.async = true; // Add function name to `names` array if (!checkName(path.node.id.name, names)) { names.push(path.node.id.name); } } this.traverse(path); }, }); } while (lastNamesLength !== names.length); // Second pass is for adding `await` statements to any function // invocations that match the given `names` array. (0, ast_types_1.visit)(ast, { visitCallExpression(path) { if (checkNames(path.node, names)) { // A "function invocation" expression, // we need to inject an `AwaitExpression` const delegate = false; const { name, parent: { node: pNode }, } = path; const expr = ast_types_1.builders.awaitExpression(path.node, delegate); if (ast_types_1.namedTypes.CallExpression.check(pNode)) { pNode.arguments[name] = expr; } else { pNode[name] = expr; } } this.traverse(path); }, }); return (0, escodegen_1.generate)(ast); } exports.degenerator = degenerator; /** * 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({ callee }, names) { let name; if (ast_types_1.namedTypes.Identifier.check(callee)) { name = callee.name; } else if (ast_types_1.namedTypes.MemberExpression.check(callee)) { if (ast_types_1.namedTypes.Identifier.check(callee.object) && ast_types_1.namedTypes.Identifier.check(callee.property)) { name = `${callee.object.name}.${callee.property.name}`; } else { return false; } } else if (ast_types_1.namedTypes.FunctionExpression.check(callee)) { if (callee.id) { name = callee.id.name; } else { return false; } } else { throw new Error(`Don't know how to get name for: ${callee.type}`); } return checkName(name, names); } function checkName(name, names) { // now that we have the `name`, check if any entries match in the `names` array for (let i = 0; i < names.length; i++) { const n = names[i]; if (util_1.types.isRegExp(n)) { if (n.test(name)) { return true; } } else if (name === n) { return true; } } return false; } //# sourceMappingURL=degenerator.js.map