index.js 7.0 KB

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