node-internal-repl-await.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. // copied from https://github.com/nodejs/node/blob/88799930794045795e8abac874730f9eba7e2300/lib/internal/repl/await.js
  2. 'use strict';
  3. const {
  4. ArrayFrom,
  5. ArrayPrototypeForEach,
  6. ArrayPrototypeIncludes,
  7. ArrayPrototypeJoin,
  8. ArrayPrototypePop,
  9. ArrayPrototypePush,
  10. FunctionPrototype,
  11. ObjectKeys,
  12. RegExpPrototypeSymbolReplace,
  13. StringPrototypeEndsWith,
  14. StringPrototypeIncludes,
  15. StringPrototypeIndexOf,
  16. StringPrototypeRepeat,
  17. StringPrototypeSplit,
  18. StringPrototypeStartsWith,
  19. SyntaxError,
  20. } = require('./node-primordials');
  21. const parser = require('acorn').Parser;
  22. const walk = require('acorn-walk');
  23. const { Recoverable } = require('repl');
  24. function isTopLevelDeclaration(state) {
  25. return state.ancestors[state.ancestors.length - 2] === state.body;
  26. }
  27. const noop = FunctionPrototype;
  28. const visitorsWithoutAncestors = {
  29. ClassDeclaration(node, state, c) {
  30. if (isTopLevelDeclaration(state)) {
  31. state.prepend(node, `${node.id.name}=`);
  32. ArrayPrototypePush(
  33. state.hoistedDeclarationStatements,
  34. `let ${node.id.name}; `
  35. );
  36. }
  37. walk.base.ClassDeclaration(node, state, c);
  38. },
  39. ForOfStatement(node, state, c) {
  40. if (node.await === true) {
  41. state.containsAwait = true;
  42. }
  43. walk.base.ForOfStatement(node, state, c);
  44. },
  45. FunctionDeclaration(node, state, c) {
  46. state.prepend(node, `${node.id.name}=`);
  47. ArrayPrototypePush(
  48. state.hoistedDeclarationStatements,
  49. `var ${node.id.name}; `
  50. );
  51. },
  52. FunctionExpression: noop,
  53. ArrowFunctionExpression: noop,
  54. MethodDefinition: noop,
  55. AwaitExpression(node, state, c) {
  56. state.containsAwait = true;
  57. walk.base.AwaitExpression(node, state, c);
  58. },
  59. ReturnStatement(node, state, c) {
  60. state.containsReturn = true;
  61. walk.base.ReturnStatement(node, state, c);
  62. },
  63. VariableDeclaration(node, state, c) {
  64. const variableKind = node.kind;
  65. const isIterableForDeclaration = ArrayPrototypeIncludes(
  66. ['ForOfStatement', 'ForInStatement'],
  67. state.ancestors[state.ancestors.length - 2].type
  68. );
  69. if (variableKind === 'var' || isTopLevelDeclaration(state)) {
  70. state.replace(
  71. node.start,
  72. node.start + variableKind.length + (isIterableForDeclaration ? 1 : 0),
  73. variableKind === 'var' && isIterableForDeclaration ?
  74. '' :
  75. 'void' + (node.declarations.length === 1 ? '' : ' (')
  76. );
  77. if (!isIterableForDeclaration) {
  78. ArrayPrototypeForEach(node.declarations, (decl) => {
  79. state.prepend(decl, '(');
  80. state.append(decl, decl.init ? ')' : '=undefined)');
  81. });
  82. if (node.declarations.length !== 1) {
  83. state.append(node.declarations[node.declarations.length - 1], ')');
  84. }
  85. }
  86. const variableIdentifiersToHoist = [
  87. ['var', []],
  88. ['let', []],
  89. ];
  90. function registerVariableDeclarationIdentifiers(node) {
  91. switch (node.type) {
  92. case 'Identifier':
  93. ArrayPrototypePush(
  94. variableIdentifiersToHoist[variableKind === 'var' ? 0 : 1][1],
  95. node.name
  96. );
  97. break;
  98. case 'ObjectPattern':
  99. ArrayPrototypeForEach(node.properties, (property) => {
  100. registerVariableDeclarationIdentifiers(property.value);
  101. });
  102. break;
  103. case 'ArrayPattern':
  104. ArrayPrototypeForEach(node.elements, (element) => {
  105. registerVariableDeclarationIdentifiers(element);
  106. });
  107. break;
  108. }
  109. }
  110. ArrayPrototypeForEach(node.declarations, (decl) => {
  111. registerVariableDeclarationIdentifiers(decl.id);
  112. });
  113. ArrayPrototypeForEach(
  114. variableIdentifiersToHoist,
  115. ({ 0: kind, 1: identifiers }) => {
  116. if (identifiers.length > 0) {
  117. ArrayPrototypePush(
  118. state.hoistedDeclarationStatements,
  119. `${kind} ${ArrayPrototypeJoin(identifiers, ', ')}; `
  120. );
  121. }
  122. }
  123. );
  124. }
  125. walk.base.VariableDeclaration(node, state, c);
  126. }
  127. };
  128. const visitors = {};
  129. for (const nodeType of ObjectKeys(walk.base)) {
  130. const callback = visitorsWithoutAncestors[nodeType] || walk.base[nodeType];
  131. visitors[nodeType] = (node, state, c) => {
  132. const isNew = node !== state.ancestors[state.ancestors.length - 1];
  133. if (isNew) {
  134. ArrayPrototypePush(state.ancestors, node);
  135. }
  136. callback(node, state, c);
  137. if (isNew) {
  138. ArrayPrototypePop(state.ancestors);
  139. }
  140. };
  141. }
  142. function processTopLevelAwait(src) {
  143. const wrapPrefix = '(async () => { ';
  144. const wrapped = `${wrapPrefix}${src} })()`;
  145. const wrappedArray = ArrayFrom(wrapped);
  146. let root;
  147. try {
  148. root = parser.parse(wrapped, { ecmaVersion: 'latest' });
  149. } catch (e) {
  150. if (StringPrototypeStartsWith(e.message, 'Unterminated '))
  151. throw new Recoverable(e);
  152. // If the parse error is before the first "await", then use the execution
  153. // error. Otherwise we must emit this parse error, making it look like a
  154. // proper syntax error.
  155. const awaitPos = StringPrototypeIndexOf(src, 'await');
  156. const errPos = e.pos - wrapPrefix.length;
  157. if (awaitPos > errPos)
  158. return null;
  159. // Convert keyword parse errors on await into their original errors when
  160. // possible.
  161. if (errPos === awaitPos + 6 &&
  162. StringPrototypeIncludes(e.message, 'Expecting Unicode escape sequence'))
  163. return null;
  164. if (errPos === awaitPos + 7 &&
  165. StringPrototypeIncludes(e.message, 'Unexpected token'))
  166. return null;
  167. const line = e.loc.line;
  168. const column = line === 1 ? e.loc.column - wrapPrefix.length : e.loc.column;
  169. let message = '\n' + StringPrototypeSplit(src, '\n')[line - 1] + '\n' +
  170. StringPrototypeRepeat(' ', column) +
  171. '^\n\n' + RegExpPrototypeSymbolReplace(/ \([^)]+\)/, e.message, '');
  172. // V8 unexpected token errors include the token string.
  173. if (StringPrototypeEndsWith(message, 'Unexpected token'))
  174. message += " '" +
  175. // Wrapper end may cause acorn to report error position after the source
  176. ((src.length - 1) >= (e.pos - wrapPrefix.length)
  177. ? src[e.pos - wrapPrefix.length]
  178. : src[src.length - 1]) +
  179. "'";
  180. // eslint-disable-next-line no-restricted-syntax
  181. throw new SyntaxError(message);
  182. }
  183. const body = root.body[0].expression.callee.body;
  184. const state = {
  185. body,
  186. ancestors: [],
  187. hoistedDeclarationStatements: [],
  188. replace(from, to, str) {
  189. for (let i = from; i < to; i++) {
  190. wrappedArray[i] = '';
  191. }
  192. if (from === to) str += wrappedArray[from];
  193. wrappedArray[from] = str;
  194. },
  195. prepend(node, str) {
  196. wrappedArray[node.start] = str + wrappedArray[node.start];
  197. },
  198. append(node, str) {
  199. wrappedArray[node.end - 1] += str;
  200. },
  201. containsAwait: false,
  202. containsReturn: false
  203. };
  204. walk.recursive(body, state, visitors);
  205. // Do not transform if
  206. // 1. False alarm: there isn't actually an await expression.
  207. // 2. There is a top-level return, which is not allowed.
  208. if (!state.containsAwait || state.containsReturn) {
  209. return null;
  210. }
  211. const last = body.body[body.body.length - 1];
  212. if (last.type === 'ExpressionStatement') {
  213. // For an expression statement of the form
  214. // ( expr ) ;
  215. // ^^^^^^^^^^ // last
  216. // ^^^^ // last.expression
  217. //
  218. // We do not want the left parenthesis before the `return` keyword;
  219. // therefore we prepend the `return (` to `last`.
  220. //
  221. // On the other hand, we do not want the right parenthesis after the
  222. // semicolon. Since there can only be more right parentheses between
  223. // last.expression.end and the semicolon, appending one more to
  224. // last.expression should be fine.
  225. state.prepend(last, 'return (');
  226. state.append(last.expression, ')');
  227. }
  228. return (
  229. ArrayPrototypeJoin(state.hoistedDeclarationStatements, '') +
  230. ArrayPrototypeJoin(wrappedArray, '')
  231. );
  232. }
  233. module.exports = {
  234. processTopLevelAwait
  235. };