degenerator.js 6.0 KB

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