1 |
|
2 | 'use strict';
|
3 |
|
4 | const {
|
5 | ArrayFrom,
|
6 | ArrayPrototypeForEach,
|
7 | ArrayPrototypeIncludes,
|
8 | ArrayPrototypeJoin,
|
9 | ArrayPrototypePop,
|
10 | ArrayPrototypePush,
|
11 | FunctionPrototype,
|
12 | ObjectKeys,
|
13 | RegExpPrototypeSymbolReplace,
|
14 | StringPrototypeEndsWith,
|
15 | StringPrototypeIncludes,
|
16 | StringPrototypeIndexOf,
|
17 | StringPrototypeRepeat,
|
18 | StringPrototypeSplit,
|
19 | StringPrototypeStartsWith,
|
20 | SyntaxError,
|
21 | } = require('./node-primordials');
|
22 |
|
23 | const parser = require('acorn').Parser;
|
24 | const walk = require('acorn-walk');
|
25 | const { Recoverable } = require('repl');
|
26 |
|
27 | function isTopLevelDeclaration(state) {
|
28 | return state.ancestors[state.ancestors.length - 2] === state.body;
|
29 | }
|
30 |
|
31 | const noop = FunctionPrototype;
|
32 | const visitorsWithoutAncestors = {
|
33 | ClassDeclaration(node, state, c) {
|
34 | if (isTopLevelDeclaration(state)) {
|
35 | state.prepend(node, `${node.id.name}=`);
|
36 | ArrayPrototypePush(
|
37 | state.hoistedDeclarationStatements,
|
38 | `let ${node.id.name}; `
|
39 | );
|
40 | }
|
41 |
|
42 | walk.base.ClassDeclaration(node, state, c);
|
43 | },
|
44 | ForOfStatement(node, state, c) {
|
45 | if (node.await === true) {
|
46 | state.containsAwait = true;
|
47 | }
|
48 | walk.base.ForOfStatement(node, state, c);
|
49 | },
|
50 | FunctionDeclaration(node, state, c) {
|
51 | state.prepend(node, `${node.id.name}=`);
|
52 | ArrayPrototypePush(
|
53 | state.hoistedDeclarationStatements,
|
54 | `var ${node.id.name}; `
|
55 | );
|
56 | },
|
57 | FunctionExpression: noop,
|
58 | ArrowFunctionExpression: noop,
|
59 | MethodDefinition: noop,
|
60 | AwaitExpression(node, state, c) {
|
61 | state.containsAwait = true;
|
62 | walk.base.AwaitExpression(node, state, c);
|
63 | },
|
64 | ReturnStatement(node, state, c) {
|
65 | state.containsReturn = true;
|
66 | walk.base.ReturnStatement(node, state, c);
|
67 | },
|
68 | VariableDeclaration(node, state, c) {
|
69 | const variableKind = node.kind;
|
70 | const isIterableForDeclaration = ArrayPrototypeIncludes(
|
71 | ['ForOfStatement', 'ForInStatement'],
|
72 | state.ancestors[state.ancestors.length - 2].type
|
73 | );
|
74 |
|
75 | if (variableKind === 'var' || isTopLevelDeclaration(state)) {
|
76 | state.replace(
|
77 | node.start,
|
78 | node.start + variableKind.length + (isIterableForDeclaration ? 1 : 0),
|
79 | variableKind === 'var' && isIterableForDeclaration ?
|
80 | '' :
|
81 | 'void' + (node.declarations.length === 1 ? '' : ' (')
|
82 | );
|
83 |
|
84 | if (!isIterableForDeclaration) {
|
85 | ArrayPrototypeForEach(node.declarations, (decl) => {
|
86 | state.prepend(decl, '(');
|
87 | state.append(decl, decl.init ? ')' : '=undefined)');
|
88 | });
|
89 |
|
90 | if (node.declarations.length !== 1) {
|
91 | state.append(node.declarations[node.declarations.length - 1], ')');
|
92 | }
|
93 | }
|
94 |
|
95 | const variableIdentifiersToHoist = [
|
96 | ['var', []],
|
97 | ['let', []],
|
98 | ];
|
99 | function registerVariableDeclarationIdentifiers(node) {
|
100 | switch (node.type) {
|
101 | case 'Identifier':
|
102 | ArrayPrototypePush(
|
103 | variableIdentifiersToHoist[variableKind === 'var' ? 0 : 1][1],
|
104 | node.name
|
105 | );
|
106 | break;
|
107 | case 'ObjectPattern':
|
108 | ArrayPrototypeForEach(node.properties, (property) => {
|
109 | registerVariableDeclarationIdentifiers(property.value);
|
110 | });
|
111 | break;
|
112 | case 'ArrayPattern':
|
113 | ArrayPrototypeForEach(node.elements, (element) => {
|
114 | registerVariableDeclarationIdentifiers(element);
|
115 | });
|
116 | break;
|
117 | }
|
118 | }
|
119 |
|
120 | ArrayPrototypeForEach(node.declarations, (decl) => {
|
121 | registerVariableDeclarationIdentifiers(decl.id);
|
122 | });
|
123 |
|
124 | ArrayPrototypeForEach(
|
125 | variableIdentifiersToHoist,
|
126 | ({ 0: kind, 1: identifiers }) => {
|
127 | if (identifiers.length > 0) {
|
128 | ArrayPrototypePush(
|
129 | state.hoistedDeclarationStatements,
|
130 | `${kind} ${ArrayPrototypeJoin(identifiers, ', ')}; `
|
131 | );
|
132 | }
|
133 | }
|
134 | );
|
135 | }
|
136 |
|
137 | walk.base.VariableDeclaration(node, state, c);
|
138 | }
|
139 | };
|
140 |
|
141 | const visitors = {};
|
142 | for (const nodeType of ObjectKeys(walk.base)) {
|
143 | const callback = visitorsWithoutAncestors[nodeType] || walk.base[nodeType];
|
144 | visitors[nodeType] = (node, state, c) => {
|
145 | const isNew = node !== state.ancestors[state.ancestors.length - 1];
|
146 | if (isNew) {
|
147 | ArrayPrototypePush(state.ancestors, node);
|
148 | }
|
149 | callback(node, state, c);
|
150 | if (isNew) {
|
151 | ArrayPrototypePop(state.ancestors);
|
152 | }
|
153 | };
|
154 | }
|
155 |
|
156 | function processTopLevelAwait(src) {
|
157 | const wrapPrefix = '(async () => { ';
|
158 | const wrapped = `${wrapPrefix}${src} })()`;
|
159 | const wrappedArray = ArrayFrom(wrapped);
|
160 | let root;
|
161 | try {
|
162 | root = parser.parse(wrapped, { ecmaVersion: 'latest' });
|
163 | } catch (e) {
|
164 | if (StringPrototypeStartsWith(e.message, 'Unterminated '))
|
165 | throw new Recoverable(e);
|
166 |
|
167 |
|
168 |
|
169 | const awaitPos = StringPrototypeIndexOf(src, 'await');
|
170 | const errPos = e.pos - wrapPrefix.length;
|
171 | if (awaitPos > errPos)
|
172 | return null;
|
173 |
|
174 |
|
175 | if (errPos === awaitPos + 6 &&
|
176 | StringPrototypeIncludes(e.message, 'Expecting Unicode escape sequence'))
|
177 | return null;
|
178 | if (errPos === awaitPos + 7 &&
|
179 | StringPrototypeIncludes(e.message, 'Unexpected token'))
|
180 | return null;
|
181 | const line = e.loc.line;
|
182 | const column = line === 1 ? e.loc.column - wrapPrefix.length : e.loc.column;
|
183 | let message = '\n' + StringPrototypeSplit(src, '\n')[line - 1] + '\n' +
|
184 | StringPrototypeRepeat(' ', column) +
|
185 | '^\n\n' + RegExpPrototypeSymbolReplace(/ \([^)]+\)/, e.message, '');
|
186 |
|
187 | if (StringPrototypeEndsWith(message, 'Unexpected token'))
|
188 | message += " '" +
|
189 |
|
190 | ((src.length - 1) >= (e.pos - wrapPrefix.length)
|
191 | ? src[e.pos - wrapPrefix.length]
|
192 | : src[src.length - 1]) +
|
193 | "'";
|
194 |
|
195 | throw new SyntaxError(message);
|
196 | }
|
197 | const body = root.body[0].expression.callee.body;
|
198 | const state = {
|
199 | body,
|
200 | ancestors: [],
|
201 | hoistedDeclarationStatements: [],
|
202 | replace(from, to, str) {
|
203 | for (let i = from; i < to; i++) {
|
204 | wrappedArray[i] = '';
|
205 | }
|
206 | if (from === to) str += wrappedArray[from];
|
207 | wrappedArray[from] = str;
|
208 | },
|
209 | prepend(node, str) {
|
210 | wrappedArray[node.start] = str + wrappedArray[node.start];
|
211 | },
|
212 | append(node, str) {
|
213 | wrappedArray[node.end - 1] += str;
|
214 | },
|
215 | containsAwait: false,
|
216 | containsReturn: false
|
217 | };
|
218 |
|
219 | walk.recursive(body, state, visitors);
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | if (!state.containsAwait || state.containsReturn) {
|
225 | return null;
|
226 | }
|
227 |
|
228 | const last = body.body[body.body.length - 1];
|
229 | if (last.type === 'ExpressionStatement') {
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | state.prepend(last, 'return (');
|
243 | state.append(last.expression, ')');
|
244 | }
|
245 |
|
246 | return (
|
247 | ArrayPrototypeJoin(state.hoistedDeclarationStatements, '') +
|
248 | ArrayPrototypeJoin(wrappedArray, '')
|
249 | );
|
250 | }
|
251 |
|
252 | module.exports = {
|
253 | processTopLevelAwait
|
254 | };
|