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