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