1 |
|
2 |
|
3 |
|
4 | import {ContextualKeyword} from "../parser/tokenizer/keywords";
|
5 | import {TokenType as tt} from "../parser/tokenizer/types";
|
6 |
|
7 | import getClassInfo, {} from "../util/getClassInfo";
|
8 | import CJSImportTransformer from "./CJSImportTransformer";
|
9 | import ESMImportTransformer from "./ESMImportTransformer";
|
10 | import FlowTransformer from "./FlowTransformer";
|
11 | import JSXTransformer from "./JSXTransformer";
|
12 | import NumericSeparatorTransformer from "./NumericSeparatorTransformer";
|
13 | import OptionalCatchBindingTransformer from "./OptionalCatchBindingTransformer";
|
14 | import OptionalChainingNullishTransformer from "./OptionalChainingNullishTransformer";
|
15 | import ReactDisplayNameTransformer from "./ReactDisplayNameTransformer";
|
16 | import ReactHotLoaderTransformer from "./ReactHotLoaderTransformer";
|
17 |
|
18 | import TypeScriptTransformer from "./TypeScriptTransformer";
|
19 |
|
20 | export default class RootTransformer {
|
21 | __init() {this.transformers = []}
|
22 |
|
23 |
|
24 | __init2() {this.generatedVariables = []}
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | constructor(
|
30 | sucraseContext,
|
31 | transforms,
|
32 | enableLegacyBabel5ModuleInterop,
|
33 | options,
|
34 | ) {;RootTransformer.prototype.__init.call(this);RootTransformer.prototype.__init2.call(this);
|
35 | this.nameManager = sucraseContext.nameManager;
|
36 | this.helperManager = sucraseContext.helperManager;
|
37 | const {tokenProcessor, importProcessor} = sucraseContext;
|
38 | this.tokens = tokenProcessor;
|
39 | this.isImportsTransformEnabled = transforms.includes("imports");
|
40 | this.isReactHotLoaderTransformEnabled = transforms.includes("react-hot-loader");
|
41 |
|
42 | this.transformers.push(
|
43 | new OptionalChainingNullishTransformer(tokenProcessor, this.nameManager),
|
44 | );
|
45 | this.transformers.push(new NumericSeparatorTransformer(tokenProcessor));
|
46 | this.transformers.push(new OptionalCatchBindingTransformer(tokenProcessor, this.nameManager));
|
47 | if (transforms.includes("jsx")) {
|
48 | this.transformers.push(
|
49 | new JSXTransformer(this, tokenProcessor, importProcessor, this.nameManager, options),
|
50 | );
|
51 | this.transformers.push(
|
52 | new ReactDisplayNameTransformer(this, tokenProcessor, importProcessor, options),
|
53 | );
|
54 | }
|
55 |
|
56 | let reactHotLoaderTransformer = null;
|
57 | if (transforms.includes("react-hot-loader")) {
|
58 | if (!options.filePath) {
|
59 | throw new Error("filePath is required when using the react-hot-loader transform.");
|
60 | }
|
61 | reactHotLoaderTransformer = new ReactHotLoaderTransformer(tokenProcessor, options.filePath);
|
62 | this.transformers.push(reactHotLoaderTransformer);
|
63 | }
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | if (transforms.includes("imports")) {
|
69 | if (importProcessor === null) {
|
70 | throw new Error("Expected non-null importProcessor with imports transform enabled.");
|
71 | }
|
72 | this.transformers.push(
|
73 | new CJSImportTransformer(
|
74 | this,
|
75 | tokenProcessor,
|
76 | importProcessor,
|
77 | this.nameManager,
|
78 | reactHotLoaderTransformer,
|
79 | enableLegacyBabel5ModuleInterop,
|
80 | transforms.includes("typescript"),
|
81 | ),
|
82 | );
|
83 | } else {
|
84 | this.transformers.push(
|
85 | new ESMImportTransformer(
|
86 | tokenProcessor,
|
87 | this.nameManager,
|
88 | reactHotLoaderTransformer,
|
89 | transforms.includes("typescript"),
|
90 | options,
|
91 | ),
|
92 | );
|
93 | }
|
94 |
|
95 | if (transforms.includes("flow")) {
|
96 | this.transformers.push(new FlowTransformer(this, tokenProcessor));
|
97 | }
|
98 | if (transforms.includes("typescript")) {
|
99 | this.transformers.push(
|
100 | new TypeScriptTransformer(this, tokenProcessor, transforms.includes("imports")),
|
101 | );
|
102 | }
|
103 | }
|
104 |
|
105 | transform() {
|
106 | this.tokens.reset();
|
107 | this.processBalancedCode();
|
108 | const shouldAddUseStrict = this.isImportsTransformEnabled;
|
109 |
|
110 | let prefix = shouldAddUseStrict ? '"use strict";' : "";
|
111 | for (const transformer of this.transformers) {
|
112 | prefix += transformer.getPrefixCode();
|
113 | }
|
114 | prefix += this.helperManager.emitHelpers();
|
115 | prefix += this.generatedVariables.map((v) => ` var ${v};`).join("");
|
116 | let suffix = "";
|
117 | for (const transformer of this.transformers) {
|
118 | suffix += transformer.getSuffixCode();
|
119 | }
|
120 | let code = this.tokens.finish();
|
121 | if (code.startsWith("#!")) {
|
122 | let newlineIndex = code.indexOf("\n");
|
123 | if (newlineIndex === -1) {
|
124 | newlineIndex = code.length;
|
125 | code += "\n";
|
126 | }
|
127 | return code.slice(0, newlineIndex + 1) + prefix + code.slice(newlineIndex + 1) + suffix;
|
128 | } else {
|
129 | return prefix + this.tokens.finish() + suffix;
|
130 | }
|
131 | }
|
132 |
|
133 | processBalancedCode() {
|
134 | let braceDepth = 0;
|
135 | let parenDepth = 0;
|
136 | while (!this.tokens.isAtEnd()) {
|
137 | if (this.tokens.matches1(tt.braceL) || this.tokens.matches1(tt.dollarBraceL)) {
|
138 | braceDepth++;
|
139 | } else if (this.tokens.matches1(tt.braceR)) {
|
140 | if (braceDepth === 0) {
|
141 | return;
|
142 | }
|
143 | braceDepth--;
|
144 | }
|
145 | if (this.tokens.matches1(tt.parenL)) {
|
146 | parenDepth++;
|
147 | } else if (this.tokens.matches1(tt.parenR)) {
|
148 | if (parenDepth === 0) {
|
149 | return;
|
150 | }
|
151 | parenDepth--;
|
152 | }
|
153 | this.processToken();
|
154 | }
|
155 | }
|
156 |
|
157 | processToken() {
|
158 | if (this.tokens.matches1(tt._class)) {
|
159 | this.processClass();
|
160 | return;
|
161 | }
|
162 | for (const transformer of this.transformers) {
|
163 | const wasProcessed = transformer.process();
|
164 | if (wasProcessed) {
|
165 | return;
|
166 | }
|
167 | }
|
168 | this.tokens.copyToken();
|
169 | }
|
170 |
|
171 | |
172 |
|
173 |
|
174 | processNamedClass() {
|
175 | if (!this.tokens.matches2(tt._class, tt.name)) {
|
176 | throw new Error("Expected identifier for exported class name.");
|
177 | }
|
178 | const name = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1);
|
179 | this.processClass();
|
180 | return name;
|
181 | }
|
182 |
|
183 | processClass() {
|
184 | const classInfo = getClassInfo(this, this.tokens, this.nameManager);
|
185 |
|
186 |
|
187 |
|
188 | const needsCommaExpression =
|
189 | classInfo.headerInfo.isExpression &&
|
190 | classInfo.staticInitializerNames.length + classInfo.instanceInitializerNames.length > 0;
|
191 |
|
192 | let className = classInfo.headerInfo.className;
|
193 | if (needsCommaExpression) {
|
194 | className = this.nameManager.claimFreeName("_class");
|
195 | this.generatedVariables.push(className);
|
196 | this.tokens.appendCode(` (${className} =`);
|
197 | }
|
198 |
|
199 | const classToken = this.tokens.currentToken();
|
200 | const contextId = classToken.contextId;
|
201 | if (contextId == null) {
|
202 | throw new Error("Expected class to have a context ID.");
|
203 | }
|
204 | this.tokens.copyExpectedToken(tt._class);
|
205 | while (!this.tokens.matchesContextIdAndLabel(tt.braceL, contextId)) {
|
206 | this.processToken();
|
207 | }
|
208 |
|
209 | this.processClassBody(classInfo, className);
|
210 |
|
211 | const staticInitializerStatements = classInfo.staticInitializerNames.map(
|
212 | (name) => `${className}.${name}()`,
|
213 | );
|
214 | if (needsCommaExpression) {
|
215 | this.tokens.appendCode(
|
216 | `, ${staticInitializerStatements.map((s) => `${s}, `).join("")}${className})`,
|
217 | );
|
218 | } else if (classInfo.staticInitializerNames.length > 0) {
|
219 | this.tokens.appendCode(` ${staticInitializerStatements.map((s) => `${s};`).join(" ")}`);
|
220 | }
|
221 | }
|
222 |
|
223 | |
224 |
|
225 |
|
226 |
|
227 | processClassBody(classInfo, className) {
|
228 | const {
|
229 | headerInfo,
|
230 | constructorInsertPos,
|
231 | constructorInitializerStatements,
|
232 | fields,
|
233 | instanceInitializerNames,
|
234 | rangesToRemove,
|
235 | } = classInfo;
|
236 | let fieldIndex = 0;
|
237 | let rangeToRemoveIndex = 0;
|
238 | const classContextId = this.tokens.currentToken().contextId;
|
239 | if (classContextId == null) {
|
240 | throw new Error("Expected non-null context ID on class.");
|
241 | }
|
242 | this.tokens.copyExpectedToken(tt.braceL);
|
243 | if (this.isReactHotLoaderTransformEnabled) {
|
244 | this.tokens.appendCode(
|
245 | "__reactstandin__regenerateByEval(key, code) {this[key] = eval(code);}",
|
246 | );
|
247 | }
|
248 |
|
249 | const needsConstructorInit =
|
250 | constructorInitializerStatements.length + instanceInitializerNames.length > 0;
|
251 |
|
252 | if (constructorInsertPos === null && needsConstructorInit) {
|
253 | const constructorInitializersCode = this.makeConstructorInitCode(
|
254 | constructorInitializerStatements,
|
255 | instanceInitializerNames,
|
256 | className,
|
257 | );
|
258 | if (headerInfo.hasSuperclass) {
|
259 | const argsName = this.nameManager.claimFreeName("args");
|
260 | this.tokens.appendCode(
|
261 | `constructor(...${argsName}) { super(...${argsName}); ${constructorInitializersCode}; }`,
|
262 | );
|
263 | } else {
|
264 | this.tokens.appendCode(`constructor() { ${constructorInitializersCode}; }`);
|
265 | }
|
266 | }
|
267 |
|
268 | while (!this.tokens.matchesContextIdAndLabel(tt.braceR, classContextId)) {
|
269 | if (fieldIndex < fields.length && this.tokens.currentIndex() === fields[fieldIndex].start) {
|
270 | let needsCloseBrace = false;
|
271 | if (this.tokens.matches1(tt.bracketL)) {
|
272 | this.tokens.copyTokenWithPrefix(`${fields[fieldIndex].initializerName}() {this`);
|
273 | } else if (this.tokens.matches1(tt.string) || this.tokens.matches1(tt.num)) {
|
274 | this.tokens.copyTokenWithPrefix(`${fields[fieldIndex].initializerName}() {this[`);
|
275 | needsCloseBrace = true;
|
276 | } else {
|
277 | this.tokens.copyTokenWithPrefix(`${fields[fieldIndex].initializerName}() {this.`);
|
278 | }
|
279 | while (this.tokens.currentIndex() < fields[fieldIndex].end) {
|
280 | if (needsCloseBrace && this.tokens.currentIndex() === fields[fieldIndex].equalsIndex) {
|
281 | this.tokens.appendCode("]");
|
282 | }
|
283 | this.processToken();
|
284 | }
|
285 | this.tokens.appendCode("}");
|
286 | fieldIndex++;
|
287 | } else if (
|
288 | rangeToRemoveIndex < rangesToRemove.length &&
|
289 | this.tokens.currentIndex() === rangesToRemove[rangeToRemoveIndex].start
|
290 | ) {
|
291 | this.tokens.removeInitialToken();
|
292 | while (this.tokens.currentIndex() < rangesToRemove[rangeToRemoveIndex].end) {
|
293 | this.tokens.removeToken();
|
294 | }
|
295 | rangeToRemoveIndex++;
|
296 | } else if (this.tokens.currentIndex() === constructorInsertPos) {
|
297 | this.tokens.copyToken();
|
298 | if (needsConstructorInit) {
|
299 | this.tokens.appendCode(
|
300 | `;${this.makeConstructorInitCode(
|
301 | constructorInitializerStatements,
|
302 | instanceInitializerNames,
|
303 | className,
|
304 | )};`,
|
305 | );
|
306 | }
|
307 | this.processToken();
|
308 | } else {
|
309 | this.processToken();
|
310 | }
|
311 | }
|
312 | this.tokens.copyExpectedToken(tt.braceR);
|
313 | }
|
314 |
|
315 | makeConstructorInitCode(
|
316 | constructorInitializerStatements,
|
317 | instanceInitializerNames,
|
318 | className,
|
319 | ) {
|
320 | return [
|
321 | ...constructorInitializerStatements,
|
322 | ...instanceInitializerNames.map((name) => `${className}.prototype.${name}.call(this)`),
|
323 | ].join(";");
|
324 | }
|
325 |
|
326 | |
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 | processPossibleArrowParamEnd() {
|
334 | if (this.tokens.matches2(tt.parenR, tt.colon) && this.tokens.tokenAtRelativeIndex(1).isType) {
|
335 | let nextNonTypeIndex = this.tokens.currentIndex() + 1;
|
336 |
|
337 | while (this.tokens.tokens[nextNonTypeIndex].isType) {
|
338 | nextNonTypeIndex++;
|
339 | }
|
340 | if (this.tokens.matches1AtIndex(nextNonTypeIndex, tt.arrow)) {
|
341 | this.tokens.removeInitialToken();
|
342 | while (this.tokens.currentIndex() < nextNonTypeIndex) {
|
343 | this.tokens.removeToken();
|
344 | }
|
345 | this.tokens.replaceTokenTrimmingLeftWhitespace(") =>");
|
346 | return true;
|
347 | }
|
348 | }
|
349 | return false;
|
350 | }
|
351 |
|
352 | |
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 | processPossibleAsyncArrowWithTypeParams() {
|
363 | if (
|
364 | !this.tokens.matchesContextual(ContextualKeyword._async) &&
|
365 | !this.tokens.matches1(tt._async)
|
366 | ) {
|
367 | return false;
|
368 | }
|
369 | const nextToken = this.tokens.tokenAtRelativeIndex(1);
|
370 | if (nextToken.type !== tt.lessThan || !nextToken.isType) {
|
371 | return false;
|
372 | }
|
373 |
|
374 | let nextNonTypeIndex = this.tokens.currentIndex() + 1;
|
375 |
|
376 | while (this.tokens.tokens[nextNonTypeIndex].isType) {
|
377 | nextNonTypeIndex++;
|
378 | }
|
379 | if (this.tokens.matches1AtIndex(nextNonTypeIndex, tt.parenL)) {
|
380 | this.tokens.replaceToken("async (");
|
381 | this.tokens.removeInitialToken();
|
382 | while (this.tokens.currentIndex() < nextNonTypeIndex) {
|
383 | this.tokens.removeToken();
|
384 | }
|
385 | this.tokens.removeToken();
|
386 |
|
387 |
|
388 | this.processBalancedCode();
|
389 | this.processToken();
|
390 | return true;
|
391 | }
|
392 | return false;
|
393 | }
|
394 |
|
395 | processPossibleTypeRange() {
|
396 | if (this.tokens.currentToken().isType) {
|
397 | this.tokens.removeInitialToken();
|
398 | while (this.tokens.currentToken().isType) {
|
399 | this.tokens.removeToken();
|
400 | }
|
401 | return true;
|
402 | }
|
403 | return false;
|
404 | }
|
405 | }
|