123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- "use strict";
- const util_1 = require("util");
- const escodegen_1 = require("escodegen");
- const esprima_1 = require("esprima");
- const ast_types_1 = require("ast-types");
- const vm2_1 = require("vm2");
- /**
- * 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 = 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;
- 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;
- 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`/`yield` statements to any function
- // invocations that match the given `names` array.
- ast_types_1.visit(ast, {
- visitCallExpression(path) {
- if (checkNames(path.node, names)) {
- // A "function invocation" expression,
- // we need to inject a `AwaitExpression`/`YieldExpression`
- 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 escodegen_1.generate(ast);
- }
- (function (degenerator) {
- function compile(code, returnName, names, options = {}) {
- const compiled = degenerator(code, names);
- const vm = new vm2_1.VM(options);
- const script = new vm2_1.VMScript(`${compiled};${returnName}`, {
- filename: options.filename,
- });
- const fn = vm.run(script);
- if (typeof fn !== 'function') {
- throw new Error(`Expected a "function" to be returned for \`${returnName}\`, but got "${typeof fn}"`);
- }
- const r = function (...args) {
- try {
- const p = fn.apply(this, args);
- if (typeof (p === null || p === void 0 ? void 0 : p.then) === 'function') {
- return p;
- }
- return Promise.resolve(p);
- }
- catch (err) {
- return Promise.reject(err);
- }
- };
- Object.defineProperty(r, 'toString', {
- value: fn.toString.bind(fn),
- enumerable: false,
- });
- return r;
- }
- degenerator.compile = compile;
- })(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.isRegExp(n)) {
- if (n.test(name)) {
- return true;
- }
- }
- else if (name === n) {
- return true;
- }
- }
- return false;
- }
- module.exports = degenerator;
- //# sourceMappingURL=index.js.map
|