UNPKG

8.03 kBJavaScriptView Raw
1// copied from https://github.com/nodejs/node/blob/88799930794045795e8abac874730f9eba7e2300/lib/internal/repl/await.js
2'use strict';
3
4const {
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
23const parser = require('acorn').Parser;
24const walk = require('acorn-walk');
25const { Recoverable } = require('repl');
26
27function isTopLevelDeclaration(state) {
28 return state.ancestors[state.ancestors.length - 2] === state.body;
29}
30
31const noop = FunctionPrototype;
32const 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
141const visitors = {};
142for (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
156function 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 // If the parse error is before the first "await", then use the execution
167 // error. Otherwise we must emit this parse error, making it look like a
168 // proper syntax error.
169 const awaitPos = StringPrototypeIndexOf(src, 'await');
170 const errPos = e.pos - wrapPrefix.length;
171 if (awaitPos > errPos)
172 return null;
173 // Convert keyword parse errors on await into their original errors when
174 // possible.
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 // V8 unexpected token errors include the token string.
187 if (StringPrototypeEndsWith(message, 'Unexpected token'))
188 message += " '" +
189 // Wrapper end may cause acorn to report error position after the source
190 ((src.length - 1) >= (e.pos - wrapPrefix.length)
191 ? src[e.pos - wrapPrefix.length]
192 : src[src.length - 1]) +
193 "'";
194 // eslint-disable-next-line no-restricted-syntax
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 // Do not transform if
222 // 1. False alarm: there isn't actually an await expression.
223 // 2. There is a top-level return, which is not allowed.
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 // For an expression statement of the form
231 // ( expr ) ;
232 // ^^^^^^^^^^ // last
233 // ^^^^ // last.expression
234 //
235 // We do not want the left parenthesis before the `return` keyword;
236 // therefore we prepend the `return (` to `last`.
237 //
238 // On the other hand, we do not want the right parenthesis after the
239 // semicolon. Since there can only be more right parentheses between
240 // last.expression.end and the semicolon, appending one more to
241 // last.expression should be fine.
242 state.prepend(last, 'return (');
243 state.append(last.expression, ')');
244 }
245
246 return (
247 ArrayPrototypeJoin(state.hoistedDeclarationStatements, '') +
248 ArrayPrototypeJoin(wrappedArray, '')
249 );
250}
251
252module.exports = {
253 processTopLevelAwait
254};