1'use strict'; 2 3const { 4 ObjectKeys, 5} = primordials; 6 7const acorn = require('internal/deps/acorn/acorn/dist/acorn'); 8const walk = require('internal/deps/acorn/acorn-walk/dist/walk'); 9const privateMethods = 10 require('internal/deps/acorn-plugins/acorn-private-methods/index'); 11const classFields = 12 require('internal/deps/acorn-plugins/acorn-class-fields/index'); 13const numericSeparator = 14 require('internal/deps/acorn-plugins/acorn-numeric-separator/index'); 15const staticClassFeatures = 16 require('internal/deps/acorn-plugins/acorn-static-class-features/index'); 17 18const parser = acorn.Parser.extend( 19 privateMethods, 20 classFields, 21 numericSeparator, 22 staticClassFeatures 23); 24 25const noop = () => {}; 26const visitorsWithoutAncestors = { 27 ClassDeclaration(node, state, c) { 28 if (state.ancestors[state.ancestors.length - 2] === state.body) { 29 state.prepend(node, `${node.id.name}=`); 30 } 31 walk.base.ClassDeclaration(node, state, c); 32 }, 33 ForOfStatement(node, state, c) { 34 if (node.await === true) { 35 state.containsAwait = true; 36 } 37 walk.base.ForOfStatement(node, state, c); 38 }, 39 FunctionDeclaration(node, state, c) { 40 state.prepend(node, `${node.id.name}=`); 41 }, 42 FunctionExpression: noop, 43 ArrowFunctionExpression: noop, 44 MethodDefinition: noop, 45 AwaitExpression(node, state, c) { 46 state.containsAwait = true; 47 walk.base.AwaitExpression(node, state, c); 48 }, 49 ReturnStatement(node, state, c) { 50 state.containsReturn = true; 51 walk.base.ReturnStatement(node, state, c); 52 }, 53 VariableDeclaration(node, state, c) { 54 if (node.kind === 'var' || 55 state.ancestors[state.ancestors.length - 2] === state.body) { 56 if (node.declarations.length === 1) { 57 state.replace(node.start, node.start + node.kind.length, 'void'); 58 } else { 59 state.replace(node.start, node.start + node.kind.length, 'void ('); 60 } 61 62 for (const decl of node.declarations) { 63 state.prepend(decl, '('); 64 state.append(decl, decl.init ? ')' : '=undefined)'); 65 } 66 67 if (node.declarations.length !== 1) { 68 state.append(node.declarations[node.declarations.length - 1], ')'); 69 } 70 } 71 72 walk.base.VariableDeclaration(node, state, c); 73 } 74}; 75 76const visitors = {}; 77for (const nodeType of ObjectKeys(walk.base)) { 78 const callback = visitorsWithoutAncestors[nodeType] || walk.base[nodeType]; 79 visitors[nodeType] = (node, state, c) => { 80 const isNew = node !== state.ancestors[state.ancestors.length - 1]; 81 if (isNew) { 82 state.ancestors.push(node); 83 } 84 callback(node, state, c); 85 if (isNew) { 86 state.ancestors.pop(); 87 } 88 }; 89} 90 91function processTopLevelAwait(src) { 92 const wrapped = `(async () => { ${src} })()`; 93 const wrappedArray = wrapped.split(''); 94 let root; 95 try { 96 root = parser.parse(wrapped, { ecmaVersion: 11 }); 97 } catch { 98 return null; 99 } 100 const body = root.body[0].expression.callee.body; 101 const state = { 102 body, 103 ancestors: [], 104 replace(from, to, str) { 105 for (let i = from; i < to; i++) { 106 wrappedArray[i] = ''; 107 } 108 if (from === to) str += wrappedArray[from]; 109 wrappedArray[from] = str; 110 }, 111 prepend(node, str) { 112 wrappedArray[node.start] = str + wrappedArray[node.start]; 113 }, 114 append(node, str) { 115 wrappedArray[node.end - 1] += str; 116 }, 117 containsAwait: false, 118 containsReturn: false 119 }; 120 121 walk.recursive(body, state, visitors); 122 123 // Do not transform if 124 // 1. False alarm: there isn't actually an await expression. 125 // 2. There is a top-level return, which is not allowed. 126 if (!state.containsAwait || state.containsReturn) { 127 return null; 128 } 129 130 const last = body.body[body.body.length - 1]; 131 if (last.type === 'ExpressionStatement') { 132 // For an expression statement of the form 133 // ( expr ) ; 134 // ^^^^^^^^^^ // last 135 // ^^^^ // last.expression 136 // 137 // We do not want the left parenthesis before the `return` keyword; 138 // therefore we prepend the `return (` to `last`. 139 // 140 // On the other hand, we do not want the right parenthesis after the 141 // semicolon. Since there can only be more right parentheses between 142 // last.expression.end and the semicolon, appending one more to 143 // last.expression should be fine. 144 state.prepend(last, 'return ('); 145 state.append(last.expression, ')'); 146 } 147 148 return wrappedArray.join(''); 149} 150 151module.exports = { 152 processTopLevelAwait 153}; 154