UNPKG

16.2 kBJavaScriptView Raw
1"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2
3
4var _keywords = require('../parser/tokenizer/keywords');
5var _types = require('../parser/tokenizer/types');
6
7var _getClassInfo = require('../util/getClassInfo'); var _getClassInfo2 = _interopRequireDefault(_getClassInfo);
8var _CJSImportTransformer = require('./CJSImportTransformer'); var _CJSImportTransformer2 = _interopRequireDefault(_CJSImportTransformer);
9var _ESMImportTransformer = require('./ESMImportTransformer'); var _ESMImportTransformer2 = _interopRequireDefault(_ESMImportTransformer);
10var _FlowTransformer = require('./FlowTransformer'); var _FlowTransformer2 = _interopRequireDefault(_FlowTransformer);
11var _JestHoistTransformer = require('./JestHoistTransformer'); var _JestHoistTransformer2 = _interopRequireDefault(_JestHoistTransformer);
12var _JSXTransformer = require('./JSXTransformer'); var _JSXTransformer2 = _interopRequireDefault(_JSXTransformer);
13var _NumericSeparatorTransformer = require('./NumericSeparatorTransformer'); var _NumericSeparatorTransformer2 = _interopRequireDefault(_NumericSeparatorTransformer);
14var _OptionalCatchBindingTransformer = require('./OptionalCatchBindingTransformer'); var _OptionalCatchBindingTransformer2 = _interopRequireDefault(_OptionalCatchBindingTransformer);
15var _OptionalChainingNullishTransformer = require('./OptionalChainingNullishTransformer'); var _OptionalChainingNullishTransformer2 = _interopRequireDefault(_OptionalChainingNullishTransformer);
16var _ReactDisplayNameTransformer = require('./ReactDisplayNameTransformer'); var _ReactDisplayNameTransformer2 = _interopRequireDefault(_ReactDisplayNameTransformer);
17var _ReactHotLoaderTransformer = require('./ReactHotLoaderTransformer'); var _ReactHotLoaderTransformer2 = _interopRequireDefault(_ReactHotLoaderTransformer);
18
19var _TypeScriptTransformer = require('./TypeScriptTransformer'); var _TypeScriptTransformer2 = _interopRequireDefault(_TypeScriptTransformer);
20
21 class RootTransformer {
22 __init() {this.transformers = []}
23
24
25 __init2() {this.generatedVariables = []}
26
27
28
29
30
31 constructor(
32 sucraseContext,
33 transforms,
34 enableLegacyBabel5ModuleInterop,
35 options,
36 ) {;RootTransformer.prototype.__init.call(this);RootTransformer.prototype.__init2.call(this);
37 this.nameManager = sucraseContext.nameManager;
38 this.helperManager = sucraseContext.helperManager;
39 const {tokenProcessor, importProcessor} = sucraseContext;
40 this.tokens = tokenProcessor;
41 this.isImportsTransformEnabled = transforms.includes("imports");
42 this.isReactHotLoaderTransformEnabled = transforms.includes("react-hot-loader");
43 this.disableESTransforms = Boolean(options.disableESTransforms);
44
45 if (!options.disableESTransforms) {
46 this.transformers.push(
47 new (0, _OptionalChainingNullishTransformer2.default)(tokenProcessor, this.nameManager),
48 );
49 this.transformers.push(new (0, _NumericSeparatorTransformer2.default)(tokenProcessor));
50 this.transformers.push(new (0, _OptionalCatchBindingTransformer2.default)(tokenProcessor, this.nameManager));
51 }
52
53 if (transforms.includes("jsx")) {
54 this.transformers.push(
55 new (0, _JSXTransformer2.default)(this, tokenProcessor, importProcessor, this.nameManager, options),
56 );
57 this.transformers.push(
58 new (0, _ReactDisplayNameTransformer2.default)(this, tokenProcessor, importProcessor, options),
59 );
60 }
61
62 let reactHotLoaderTransformer = null;
63 if (transforms.includes("react-hot-loader")) {
64 if (!options.filePath) {
65 throw new Error("filePath is required when using the react-hot-loader transform.");
66 }
67 reactHotLoaderTransformer = new (0, _ReactHotLoaderTransformer2.default)(tokenProcessor, options.filePath);
68 this.transformers.push(reactHotLoaderTransformer);
69 }
70
71 // Note that we always want to enable the imports transformer, even when the import transform
72 // itself isn't enabled, since we need to do type-only import pruning for both Flow and
73 // TypeScript.
74 if (transforms.includes("imports")) {
75 if (importProcessor === null) {
76 throw new Error("Expected non-null importProcessor with imports transform enabled.");
77 }
78 this.transformers.push(
79 new (0, _CJSImportTransformer2.default)(
80 this,
81 tokenProcessor,
82 importProcessor,
83 this.nameManager,
84 reactHotLoaderTransformer,
85 enableLegacyBabel5ModuleInterop,
86 transforms.includes("typescript"),
87 ),
88 );
89 } else {
90 this.transformers.push(
91 new (0, _ESMImportTransformer2.default)(
92 tokenProcessor,
93 this.nameManager,
94 reactHotLoaderTransformer,
95 transforms.includes("typescript"),
96 options,
97 ),
98 );
99 }
100
101 if (transforms.includes("flow")) {
102 this.transformers.push(new (0, _FlowTransformer2.default)(this, tokenProcessor));
103 }
104 if (transforms.includes("typescript")) {
105 this.transformers.push(
106 new (0, _TypeScriptTransformer2.default)(this, tokenProcessor, transforms.includes("imports")),
107 );
108 }
109 if (transforms.includes("jest")) {
110 this.transformers.push(
111 new (0, _JestHoistTransformer2.default)(this, tokenProcessor, this.nameManager, importProcessor),
112 );
113 }
114 }
115
116 transform() {
117 this.tokens.reset();
118 this.processBalancedCode();
119 const shouldAddUseStrict = this.isImportsTransformEnabled;
120 // "use strict" always needs to be first, so override the normal transformer order.
121 let prefix = shouldAddUseStrict ? '"use strict";' : "";
122 for (const transformer of this.transformers) {
123 prefix += transformer.getPrefixCode();
124 }
125 prefix += this.helperManager.emitHelpers();
126 prefix += this.generatedVariables.map((v) => ` var ${v};`).join("");
127 for (const transformer of this.transformers) {
128 prefix += transformer.getHoistedCode();
129 }
130 let suffix = "";
131 for (const transformer of this.transformers) {
132 suffix += transformer.getSuffixCode();
133 }
134 let code = this.tokens.finish();
135 if (code.startsWith("#!")) {
136 let newlineIndex = code.indexOf("\n");
137 if (newlineIndex === -1) {
138 newlineIndex = code.length;
139 code += "\n";
140 }
141 return code.slice(0, newlineIndex + 1) + prefix + code.slice(newlineIndex + 1) + suffix;
142 } else {
143 return prefix + this.tokens.finish() + suffix;
144 }
145 }
146
147 processBalancedCode() {
148 let braceDepth = 0;
149 let parenDepth = 0;
150 while (!this.tokens.isAtEnd()) {
151 if (this.tokens.matches1(_types.TokenType.braceL) || this.tokens.matches1(_types.TokenType.dollarBraceL)) {
152 braceDepth++;
153 } else if (this.tokens.matches1(_types.TokenType.braceR)) {
154 if (braceDepth === 0) {
155 return;
156 }
157 braceDepth--;
158 }
159 if (this.tokens.matches1(_types.TokenType.parenL)) {
160 parenDepth++;
161 } else if (this.tokens.matches1(_types.TokenType.parenR)) {
162 if (parenDepth === 0) {
163 return;
164 }
165 parenDepth--;
166 }
167 this.processToken();
168 }
169 }
170
171 processToken() {
172 if (this.tokens.matches1(_types.TokenType._class)) {
173 this.processClass();
174 return;
175 }
176 for (const transformer of this.transformers) {
177 const wasProcessed = transformer.process();
178 if (wasProcessed) {
179 return;
180 }
181 }
182 this.tokens.copyToken();
183 }
184
185 /**
186 * Skip past a class with a name and return that name.
187 */
188 processNamedClass() {
189 if (!this.tokens.matches2(_types.TokenType._class, _types.TokenType.name)) {
190 throw new Error("Expected identifier for exported class name.");
191 }
192 const name = this.tokens.identifierNameAtIndex(this.tokens.currentIndex() + 1);
193 this.processClass();
194 return name;
195 }
196
197 processClass() {
198 const classInfo = _getClassInfo2.default.call(void 0, this, this.tokens, this.nameManager, this.disableESTransforms);
199
200 // Both static and instance initializers need a class name to use to invoke the initializer, so
201 // assign to one if necessary.
202 const needsCommaExpression =
203 classInfo.headerInfo.isExpression &&
204 classInfo.staticInitializerNames.length + classInfo.instanceInitializerNames.length > 0;
205
206 let className = classInfo.headerInfo.className;
207 if (needsCommaExpression) {
208 className = this.nameManager.claimFreeName("_class");
209 this.generatedVariables.push(className);
210 this.tokens.appendCode(` (${className} =`);
211 }
212
213 const classToken = this.tokens.currentToken();
214 const contextId = classToken.contextId;
215 if (contextId == null) {
216 throw new Error("Expected class to have a context ID.");
217 }
218 this.tokens.copyExpectedToken(_types.TokenType._class);
219 while (!this.tokens.matchesContextIdAndLabel(_types.TokenType.braceL, contextId)) {
220 this.processToken();
221 }
222
223 this.processClassBody(classInfo, className);
224
225 const staticInitializerStatements = classInfo.staticInitializerNames.map(
226 (name) => `${className}.${name}()`,
227 );
228 if (needsCommaExpression) {
229 this.tokens.appendCode(
230 `, ${staticInitializerStatements.map((s) => `${s}, `).join("")}${className})`,
231 );
232 } else if (classInfo.staticInitializerNames.length > 0) {
233 this.tokens.appendCode(` ${staticInitializerStatements.map((s) => `${s};`).join(" ")}`);
234 }
235 }
236
237 /**
238 * We want to just handle class fields in all contexts, since TypeScript supports them. Later,
239 * when some JS implementations support class fields, this should be made optional.
240 */
241 processClassBody(classInfo, className) {
242 const {
243 headerInfo,
244 constructorInsertPos,
245 constructorInitializerStatements,
246 fields,
247 instanceInitializerNames,
248 rangesToRemove,
249 } = classInfo;
250 let fieldIndex = 0;
251 let rangeToRemoveIndex = 0;
252 const classContextId = this.tokens.currentToken().contextId;
253 if (classContextId == null) {
254 throw new Error("Expected non-null context ID on class.");
255 }
256 this.tokens.copyExpectedToken(_types.TokenType.braceL);
257 if (this.isReactHotLoaderTransformEnabled) {
258 this.tokens.appendCode(
259 "__reactstandin__regenerateByEval(key, code) {this[key] = eval(code);}",
260 );
261 }
262
263 const needsConstructorInit =
264 constructorInitializerStatements.length + instanceInitializerNames.length > 0;
265
266 if (constructorInsertPos === null && needsConstructorInit) {
267 const constructorInitializersCode = this.makeConstructorInitCode(
268 constructorInitializerStatements,
269 instanceInitializerNames,
270 className,
271 );
272 if (headerInfo.hasSuperclass) {
273 const argsName = this.nameManager.claimFreeName("args");
274 this.tokens.appendCode(
275 `constructor(...${argsName}) { super(...${argsName}); ${constructorInitializersCode}; }`,
276 );
277 } else {
278 this.tokens.appendCode(`constructor() { ${constructorInitializersCode}; }`);
279 }
280 }
281
282 while (!this.tokens.matchesContextIdAndLabel(_types.TokenType.braceR, classContextId)) {
283 if (fieldIndex < fields.length && this.tokens.currentIndex() === fields[fieldIndex].start) {
284 let needsCloseBrace = false;
285 if (this.tokens.matches1(_types.TokenType.bracketL)) {
286 this.tokens.copyTokenWithPrefix(`${fields[fieldIndex].initializerName}() {this`);
287 } else if (this.tokens.matches1(_types.TokenType.string) || this.tokens.matches1(_types.TokenType.num)) {
288 this.tokens.copyTokenWithPrefix(`${fields[fieldIndex].initializerName}() {this[`);
289 needsCloseBrace = true;
290 } else {
291 this.tokens.copyTokenWithPrefix(`${fields[fieldIndex].initializerName}() {this.`);
292 }
293 while (this.tokens.currentIndex() < fields[fieldIndex].end) {
294 if (needsCloseBrace && this.tokens.currentIndex() === fields[fieldIndex].equalsIndex) {
295 this.tokens.appendCode("]");
296 }
297 this.processToken();
298 }
299 this.tokens.appendCode("}");
300 fieldIndex++;
301 } else if (
302 rangeToRemoveIndex < rangesToRemove.length &&
303 this.tokens.currentIndex() >= rangesToRemove[rangeToRemoveIndex].start
304 ) {
305 if (this.tokens.currentIndex() < rangesToRemove[rangeToRemoveIndex].end) {
306 this.tokens.removeInitialToken();
307 }
308 while (this.tokens.currentIndex() < rangesToRemove[rangeToRemoveIndex].end) {
309 this.tokens.removeToken();
310 }
311 rangeToRemoveIndex++;
312 } else if (this.tokens.currentIndex() === constructorInsertPos) {
313 this.tokens.copyToken();
314 if (needsConstructorInit) {
315 this.tokens.appendCode(
316 `;${this.makeConstructorInitCode(
317 constructorInitializerStatements,
318 instanceInitializerNames,
319 className,
320 )};`,
321 );
322 }
323 this.processToken();
324 } else {
325 this.processToken();
326 }
327 }
328 this.tokens.copyExpectedToken(_types.TokenType.braceR);
329 }
330
331 makeConstructorInitCode(
332 constructorInitializerStatements,
333 instanceInitializerNames,
334 className,
335 ) {
336 return [
337 ...constructorInitializerStatements,
338 ...instanceInitializerNames.map((name) => `${className}.prototype.${name}.call(this)`),
339 ].join(";");
340 }
341
342 /**
343 * Normally it's ok to simply remove type tokens, but we need to be more careful when dealing with
344 * arrow function return types since they can confuse the parser. In that case, we want to move
345 * the close-paren to the same line as the arrow.
346 *
347 * See https://github.com/alangpierce/sucrase/issues/391 for more details.
348 */
349 processPossibleArrowParamEnd() {
350 if (this.tokens.matches2(_types.TokenType.parenR, _types.TokenType.colon) && this.tokens.tokenAtRelativeIndex(1).isType) {
351 let nextNonTypeIndex = this.tokens.currentIndex() + 1;
352 // Look ahead to see if this is an arrow function or something else.
353 while (this.tokens.tokens[nextNonTypeIndex].isType) {
354 nextNonTypeIndex++;
355 }
356 if (this.tokens.matches1AtIndex(nextNonTypeIndex, _types.TokenType.arrow)) {
357 this.tokens.removeInitialToken();
358 while (this.tokens.currentIndex() < nextNonTypeIndex) {
359 this.tokens.removeToken();
360 }
361 this.tokens.replaceTokenTrimmingLeftWhitespace(") =>");
362 return true;
363 }
364 }
365 return false;
366 }
367
368 /**
369 * An async arrow function might be of the form:
370 *
371 * async <
372 * T
373 * >() => {}
374 *
375 * in which case, removing the type parameters will cause a syntax error. Detect this case and
376 * move the open-paren earlier.
377 */
378 processPossibleAsyncArrowWithTypeParams() {
379 if (
380 !this.tokens.matchesContextual(_keywords.ContextualKeyword._async) &&
381 !this.tokens.matches1(_types.TokenType._async)
382 ) {
383 return false;
384 }
385 const nextToken = this.tokens.tokenAtRelativeIndex(1);
386 if (nextToken.type !== _types.TokenType.lessThan || !nextToken.isType) {
387 return false;
388 }
389
390 let nextNonTypeIndex = this.tokens.currentIndex() + 1;
391 // Look ahead to see if this is an arrow function or something else.
392 while (this.tokens.tokens[nextNonTypeIndex].isType) {
393 nextNonTypeIndex++;
394 }
395 if (this.tokens.matches1AtIndex(nextNonTypeIndex, _types.TokenType.parenL)) {
396 this.tokens.replaceToken("async (");
397 this.tokens.removeInitialToken();
398 while (this.tokens.currentIndex() < nextNonTypeIndex) {
399 this.tokens.removeToken();
400 }
401 this.tokens.removeToken();
402 // We ate a ( token, so we need to process the tokens in between and then the ) token so that
403 // we remain balanced.
404 this.processBalancedCode();
405 this.processToken();
406 return true;
407 }
408 return false;
409 }
410
411 processPossibleTypeRange() {
412 if (this.tokens.currentToken().isType) {
413 this.tokens.removeInitialToken();
414 while (this.tokens.currentToken().isType) {
415 this.tokens.removeToken();
416 }
417 return true;
418 }
419 return false;
420 }
421} exports.default = RootTransformer;