index.js
  1  "use strict";
  2  var __importDefault = (this && this.__importDefault) || function (mod) {
  3      return (mod && mod.__esModule) ? mod : { "default": mod };
  4  };
  5  const util_1 = require("util");
  6  const escodegen_1 = require("escodegen");
  7  const esprima_1 = require("esprima");
  8  const ast_types_1 = require("ast-types");
  9  const vm_1 = require("vm");
 10  const supports_async_1 = __importDefault(require("./supports-async"));
 11  const generator_to_promise_1 = __importDefault(require("./generator-to-promise"));
 12  /**
 13   * Compiles sync JavaScript code into JavaScript with async Functions.
 14   *
 15   * @param {String} code JavaScript string to convert
 16   * @param {Array} names Array of function names to add `await` operators to
 17   * @return {String} Converted JavaScript string with async/await injected
 18   * @api public
 19   */
 20  function degenerator(code, _names, { output = 'async' } = {}) {
 21      if (!Array.isArray(_names)) {
 22          throw new TypeError('an array of async function "names" is required');
 23      }
 24      // Duplicate the `names` array since it's rude to augment the user args
 25      const names = _names.slice(0);
 26      const ast = esprima_1.parseScript(code);
 27      // First pass is to find the `function` nodes and turn them into async or
 28      // generator functions only if their body includes `CallExpressions` to
 29      // function in `names`. We also add the names of the functions to the `names`
 30      // array. We'll iterate several time, as every iteration might add new items
 31      // to the `names` array, until no new names were added in the iteration.
 32      let lastNamesLength = 0;
 33      do {
 34          lastNamesLength = names.length;
 35          ast_types_1.visit(ast, {
 36              visitVariableDeclaration(path) {
 37                  if (path.node.declarations) {
 38                      for (let i = 0; i < path.node.declarations.length; i++) {
 39                          const declaration = path.node.declarations[i];
 40                          if (ast_types_1.namedTypes.VariableDeclarator.check(declaration) &&
 41                              ast_types_1.namedTypes.Identifier.check(declaration.init) &&
 42                              ast_types_1.namedTypes.Identifier.check(declaration.id) &&
 43                              checkName(declaration.init.name, names) &&
 44                              !checkName(declaration.id.name, names)) {
 45                              names.push(declaration.id.name);
 46                          }
 47                      }
 48                  }
 49                  return false;
 50              },
 51              visitAssignmentExpression(path) {
 52                  if (ast_types_1.namedTypes.Identifier.check(path.node.left) &&
 53                      ast_types_1.namedTypes.Identifier.check(path.node.right) &&
 54                      checkName(path.node.right.name, names) &&
 55                      !checkName(path.node.left.name, names)) {
 56                      names.push(path.node.left.name);
 57                  }
 58                  return false;
 59              },
 60              visitFunction(path) {
 61                  if (path.node.id) {
 62                      let shouldDegenerate = false;
 63                      ast_types_1.visit(path.node, {
 64                          visitCallExpression(path) {
 65                              if (checkNames(path.node, names)) {
 66                                  shouldDegenerate = true;
 67                              }
 68                              return false;
 69                          }
 70                      });
 71                      if (!shouldDegenerate) {
 72                          return false;
 73                      }
 74                      // Got a "function" expression/statement,
 75                      // convert it into an async or generator function
 76                      if (output === 'async') {
 77                          path.node.async = true;
 78                      }
 79                      else if (output === 'generator') {
 80                          path.node.generator = true;
 81                      }
 82                      // Add function name to `names` array
 83                      if (!checkName(path.node.id.name, names)) {
 84                          names.push(path.node.id.name);
 85                      }
 86                  }
 87                  this.traverse(path);
 88              }
 89          });
 90      } while (lastNamesLength !== names.length);
 91      // Second pass is for adding `await`/`yield` statements to any function
 92      // invocations that match the given `names` array.
 93      ast_types_1.visit(ast, {
 94          visitCallExpression(path) {
 95              if (checkNames(path.node, names)) {
 96                  // A "function invocation" expression,
 97                  // we need to inject a `AwaitExpression`/`YieldExpression`
 98                  const delegate = false;
 99                  const { name, parent: { node: pNode } } = path;
100                  let expr;
101                  if (output === 'async') {
102                      expr = ast_types_1.builders.awaitExpression(path.node, delegate);
103                  }
104                  else if (output === 'generator') {
105                      expr = ast_types_1.builders.yieldExpression(path.node, delegate);
106                  }
107                  else {
108                      throw new Error('Only "async" and "generator" are allowd `output` values');
109                  }
110                  if (ast_types_1.namedTypes.CallExpression.check(pNode)) {
111                      pNode.arguments[name] = expr;
112                  }
113                  else {
114                      pNode[name] = expr;
115                  }
116              }
117              this.traverse(path);
118          }
119      });
120      return escodegen_1.generate(ast);
121  }
122  (function (degenerator) {
123      degenerator.supportsAsync = supports_async_1.default;
124      function compile(code, returnName, names, options = {}) {
125          const output = supports_async_1.default ? 'async' : 'generator';
126          const compiled = degenerator(code, names, Object.assign(Object.assign({}, options), { output }));
127          const fn = vm_1.runInNewContext(`${compiled};${returnName}`, options.sandbox, options);
128          if (typeof fn !== 'function') {
129              throw new Error(`Expected a "function" to be returned for \`${returnName}\`, but got "${typeof fn}"`);
130          }
131          if (isAsyncFunction(fn)) {
132              return fn;
133          }
134          else {
135              const rtn = generator_to_promise_1.default(fn);
136              Object.defineProperty(rtn, 'toString', {
137                  value: fn.toString.bind(fn),
138                  enumerable: false
139              });
140              return rtn;
141          }
142      }
143      degenerator.compile = compile;
144  })(degenerator || (degenerator = {}));
145  function isAsyncFunction(fn) {
146      return typeof fn === 'function' && fn.constructor.name === 'AsyncFunction';
147  }
148  /**
149   * Returns `true` if `node` has a matching name to one of the entries in the
150   * `names` array.
151   *
152   * @param {types.Node} node
153   * @param {Array} names Array of function names to return true for
154   * @return {Boolean}
155   * @api private
156   */
157  function checkNames({ callee }, names) {
158      let name;
159      if (ast_types_1.namedTypes.Identifier.check(callee)) {
160          name = callee.name;
161      }
162      else if (ast_types_1.namedTypes.MemberExpression.check(callee)) {
163          if (ast_types_1.namedTypes.Identifier.check(callee.object) &&
164              ast_types_1.namedTypes.Identifier.check(callee.property)) {
165              name = `${callee.object.name}.${callee.property.name}`;
166          }
167          else {
168              return false;
169          }
170      }
171      else if (ast_types_1.namedTypes.FunctionExpression.check(callee)) {
172          if (callee.id) {
173              name = callee.id.name;
174          }
175          else {
176              return false;
177          }
178      }
179      else {
180          throw new Error(`Don't know how to get name for: ${callee.type}`);
181      }
182      return checkName(name, names);
183  }
184  function checkName(name, names) {
185      // now that we have the `name`, check if any entries match in the `names` array
186      for (let i = 0; i < names.length; i++) {
187          const n = names[i];
188          if (util_1.isRegExp(n)) {
189              if (n.test(name)) {
190                  return true;
191              }
192          }
193          else if (name === n) {
194              return true;
195          }
196      }
197      return false;
198  }
199  module.exports = degenerator;
200  //# sourceMappingURL=index.js.map