UNPKG

23.1 kBJavaScriptView Raw
1"use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }Object.defineProperty(exports, "__esModule", {value: true});
2
3var _tokenizer = require('../parser/tokenizer');
4var _keywords = require('../parser/tokenizer/keywords');
5var _types = require('../parser/tokenizer/types');
6
7
8
9var _Transformer = require('./Transformer'); var _Transformer2 = _interopRequireDefault(_Transformer);
10
11/**
12 * Class for editing import statements when we are transforming to commonjs.
13 */
14 class CJSImportTransformer extends _Transformer2.default {
15 __init() {this.hadExport = false}
16 __init2() {this.hadNamedExport = false}
17 __init3() {this.hadDefaultExport = false}
18
19 constructor(
20 rootTransformer,
21 tokens,
22 importProcessor,
23 nameManager,
24 reactHotLoaderTransformer,
25 enableLegacyBabel5ModuleInterop,
26 ) {
27 super();this.rootTransformer = rootTransformer;this.tokens = tokens;this.importProcessor = importProcessor;this.nameManager = nameManager;this.reactHotLoaderTransformer = reactHotLoaderTransformer;this.enableLegacyBabel5ModuleInterop = enableLegacyBabel5ModuleInterop;CJSImportTransformer.prototype.__init.call(this);CJSImportTransformer.prototype.__init2.call(this);CJSImportTransformer.prototype.__init3.call(this);;
28 }
29
30 getPrefixCode() {
31 let prefix = this.importProcessor.getPrefixCode();
32 if (this.hadExport) {
33 prefix += 'Object.defineProperty(exports, "__esModule", {value: true});';
34 }
35 return prefix;
36 }
37
38 getSuffixCode() {
39 if (this.enableLegacyBabel5ModuleInterop && this.hadDefaultExport && !this.hadNamedExport) {
40 return "\nmodule.exports = exports.default;\n";
41 }
42 return "";
43 }
44
45 process() {
46 // TypeScript `import foo = require('foo');` should always just be translated to plain require.
47 if (this.tokens.matches3(_types.TokenType._import, _types.TokenType.name, _types.TokenType.eq)) {
48 this.tokens.replaceToken("const");
49 return true;
50 }
51 if (this.tokens.matches1(_types.TokenType._import)) {
52 this.processImport();
53 return true;
54 }
55 if (this.tokens.matches2(_types.TokenType._export, _types.TokenType.eq)) {
56 this.tokens.replaceToken("module.exports");
57 return true;
58 }
59 if (this.tokens.matches1(_types.TokenType._export) && !this.tokens.currentToken().isType) {
60 this.hadExport = true;
61 return this.processExport();
62 }
63 if (this.tokens.matches1(_types.TokenType.name) || this.tokens.matches1(_types.TokenType.jsxName)) {
64 return this.processIdentifier();
65 }
66 if (this.tokens.matches1(_types.TokenType.eq)) {
67 return this.processAssignment();
68 }
69 return false;
70 }
71
72 /**
73 * Transform this:
74 * import foo, {bar} from 'baz';
75 * into
76 * var _baz = require('baz'); var _baz2 = _interopRequireDefault(_baz);
77 *
78 * The import code was already generated in the import preprocessing step, so
79 * we just need to look it up.
80 */
81 processImport() {
82 if (this.tokens.matches2(_types.TokenType._import, _types.TokenType.parenL)) {
83 this.tokens.replaceToken("Promise.resolve().then(() => require");
84 const contextId = this.tokens.currentToken().contextId;
85 if (contextId == null) {
86 throw new Error("Expected context ID on dynamic import invocation.");
87 }
88 this.tokens.copyToken();
89 while (!this.tokens.matchesContextIdAndLabel(_types.TokenType.parenR, contextId)) {
90 this.rootTransformer.processToken();
91 }
92 this.tokens.replaceToken("))");
93 return;
94 }
95
96 const wasOnlyTypes = this.removeImportAndDetectIfType();
97
98 if (wasOnlyTypes) {
99 this.tokens.removeToken();
100 } else {
101 const path = this.tokens.stringValue();
102 this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
103 this.tokens.appendCode(this.importProcessor.claimImportCode(path));
104 }
105 if (this.tokens.matches1(_types.TokenType.semi)) {
106 this.tokens.removeToken();
107 }
108 }
109
110 /**
111 * Erase this import, and return true if it was either of the form "import type" or contained only
112 * "type" named imports. Such imports should not even do a side-effect import.
113 *
114 * The position should end at the import string.
115 */
116 removeImportAndDetectIfType() {
117 this.tokens.removeInitialToken();
118 if (
119 this.tokens.matchesContextual(_keywords.ContextualKeyword._type) &&
120 !this.tokens.matches1AtIndex(this.tokens.currentIndex() + 1, _types.TokenType.comma) &&
121 !this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 1, _keywords.ContextualKeyword._from)
122 ) {
123 // This is an "import type" statement, so exit early.
124 this.removeRemainingImport();
125 return true;
126 }
127
128 if (this.tokens.matches1(_types.TokenType.name) || this.tokens.matches1(_types.TokenType.star)) {
129 // We have a default import or namespace import, so there must be some
130 // non-type import.
131 this.removeRemainingImport();
132 return false;
133 }
134
135 if (this.tokens.matches1(_types.TokenType.string)) {
136 // This is a bare import, so we should proceed with the import.
137 return false;
138 }
139
140 let foundNonType = false;
141 while (!this.tokens.matches1(_types.TokenType.string)) {
142 // Check if any named imports are of the form "foo" or "foo as bar", with
143 // no leading "type".
144 if ((!foundNonType && this.tokens.matches1(_types.TokenType.braceL)) || this.tokens.matches1(_types.TokenType.comma)) {
145 this.tokens.removeToken();
146 if (
147 this.tokens.matches2(_types.TokenType.name, _types.TokenType.comma) ||
148 this.tokens.matches2(_types.TokenType.name, _types.TokenType.braceR) ||
149 this.tokens.matches4(_types.TokenType.name, _types.TokenType.name, _types.TokenType.name, _types.TokenType.comma) ||
150 this.tokens.matches4(_types.TokenType.name, _types.TokenType.name, _types.TokenType.name, _types.TokenType.braceR)
151 ) {
152 foundNonType = true;
153 }
154 }
155 this.tokens.removeToken();
156 }
157 return !foundNonType;
158 }
159
160 removeRemainingImport() {
161 while (!this.tokens.matches1(_types.TokenType.string)) {
162 this.tokens.removeToken();
163 }
164 }
165
166 processIdentifier() {
167 const token = this.tokens.currentToken();
168 if (token.shadowsGlobal) {
169 return false;
170 }
171
172 if (token.identifierRole === _tokenizer.IdentifierRole.ObjectShorthand) {
173 return this.processObjectShorthand();
174 }
175
176 if (token.identifierRole !== _tokenizer.IdentifierRole.Access) {
177 return false;
178 }
179 const replacement = this.importProcessor.getIdentifierReplacement(
180 this.tokens.identifierNameForToken(token),
181 );
182 if (!replacement) {
183 return false;
184 }
185 // Tolerate any number of closing parens while looking for an opening paren
186 // that indicates a function call.
187 let possibleOpenParenIndex = this.tokens.currentIndex() + 1;
188 while (
189 possibleOpenParenIndex < this.tokens.tokens.length &&
190 this.tokens.tokens[possibleOpenParenIndex].type === _types.TokenType.parenR
191 ) {
192 possibleOpenParenIndex++;
193 }
194 // Avoid treating imported functions as methods of their `exports` object
195 // by using `(0, f)` when the identifier is in a paren expression. Else
196 // use `Function.prototype.call` when the identifier is a guaranteed
197 // function call. When using `call`, pass undefined as the context.
198 if (this.tokens.tokens[possibleOpenParenIndex].type === _types.TokenType.parenL) {
199 if (
200 this.tokens.tokenAtRelativeIndex(1).type === _types.TokenType.parenL &&
201 this.tokens.tokenAtRelativeIndex(-1).type !== _types.TokenType._new
202 ) {
203 this.tokens.replaceToken(`${replacement}.call(void 0, `);
204 // Remove the old paren.
205 this.tokens.removeToken();
206 // Balance out the new paren.
207 this.rootTransformer.processBalancedCode();
208 this.tokens.copyExpectedToken(_types.TokenType.parenR);
209 } else {
210 // See here: http://2ality.com/2015/12/references.html
211 this.tokens.replaceToken(`(0, ${replacement})`);
212 }
213 } else {
214 this.tokens.replaceToken(replacement);
215 }
216 return true;
217 }
218
219 processObjectShorthand() {
220 const identifier = this.tokens.identifierName();
221 const replacement = this.importProcessor.getIdentifierReplacement(identifier);
222 if (!replacement) {
223 return false;
224 }
225 this.tokens.replaceToken(`${identifier}: ${replacement}`);
226 return true;
227 }
228
229 processExport() {
230 if (
231 this.tokens.matches2(_types.TokenType._export, _types.TokenType._enum) ||
232 this.tokens.matches3(_types.TokenType._export, _types.TokenType._const, _types.TokenType._enum)
233 ) {
234 // Let the TypeScript transform handle it.
235 return false;
236 }
237 if (this.tokens.matches2(_types.TokenType._export, _types.TokenType._default)) {
238 this.processExportDefault();
239 this.hadDefaultExport = true;
240 return true;
241 }
242 this.hadNamedExport = true;
243 if (
244 this.tokens.matches2(_types.TokenType._export, _types.TokenType._var) ||
245 this.tokens.matches2(_types.TokenType._export, _types.TokenType._let) ||
246 this.tokens.matches2(_types.TokenType._export, _types.TokenType._const)
247 ) {
248 this.processExportVar();
249 return true;
250 } else if (
251 this.tokens.matches2(_types.TokenType._export, _types.TokenType._function) ||
252 // export async function
253 this.tokens.matches3(_types.TokenType._export, _types.TokenType.name, _types.TokenType._function)
254 ) {
255 this.processExportFunction();
256 return true;
257 } else if (
258 this.tokens.matches2(_types.TokenType._export, _types.TokenType._class) ||
259 this.tokens.matches3(_types.TokenType._export, _types.TokenType._abstract, _types.TokenType._class)
260 ) {
261 this.processExportClass();
262 return true;
263 } else if (this.tokens.matches2(_types.TokenType._export, _types.TokenType.braceL)) {
264 this.processExportBindings();
265 return true;
266 } else if (this.tokens.matches2(_types.TokenType._export, _types.TokenType.star)) {
267 this.processExportStar();
268 return true;
269 } else {
270 throw new Error("Unrecognized export syntax.");
271 }
272 }
273
274 processAssignment() {
275 const index = this.tokens.currentIndex();
276 const identifierToken = this.tokens.tokens[index - 1];
277 // If the LHS is a type identifier, this must be a declaration like `let a: b = c;`,
278 // with `b` as the identifier, so nothing needs to be done in that case.
279 if (identifierToken.isType || identifierToken.type !== _types.TokenType.name) {
280 return false;
281 }
282 if (index >= 2 && this.tokens.matches1AtIndex(index - 2, _types.TokenType.dot)) {
283 return false;
284 }
285 if (index >= 2 && [_types.TokenType._var, _types.TokenType._let, _types.TokenType._const].includes(this.tokens.tokens[index - 2].type)) {
286 // Declarations don't need an extra assignment. This doesn't avoid the
287 // assignment for comma-separated declarations, but it's still correct
288 // since the assignment is just redundant.
289 return false;
290 }
291 const exportedName = this.importProcessor.resolveExportBinding(
292 this.tokens.identifierNameForToken(identifierToken),
293 );
294 if (!exportedName) {
295 return false;
296 }
297 this.tokens.copyToken();
298 this.tokens.appendCode(` exports.${exportedName} =`);
299 return true;
300 }
301
302 processExportDefault() {
303 if (
304 this.tokens.matches4(_types.TokenType._export, _types.TokenType._default, _types.TokenType._function, _types.TokenType.name) ||
305 // export default async function
306 this.tokens.matches5(_types.TokenType._export, _types.TokenType._default, _types.TokenType.name, _types.TokenType._function, _types.TokenType.name)
307 ) {
308 this.tokens.removeInitialToken();
309 this.tokens.removeToken();
310 // Named function export case: change it to a top-level function
311 // declaration followed by exports statement.
312 const name = this.processNamedFunction();
313 this.tokens.appendCode(` exports.default = ${name};`);
314 } else if (
315 this.tokens.matches4(_types.TokenType._export, _types.TokenType._default, _types.TokenType._class, _types.TokenType.name) ||
316 this.tokens.matches5(_types.TokenType._export, _types.TokenType._default, _types.TokenType._abstract, _types.TokenType._class, _types.TokenType.name)
317 ) {
318 this.tokens.removeInitialToken();
319 this.tokens.removeToken();
320 if (this.tokens.matches1(_types.TokenType._abstract)) {
321 this.tokens.removeToken();
322 }
323 const name = this.rootTransformer.processNamedClass();
324 this.tokens.appendCode(` exports.default = ${name};`);
325 } else if (this.tokens.matches3(_types.TokenType._export, _types.TokenType._default, _types.TokenType.at)) {
326 throw new Error("Export default statements with decorators are not yet supported.");
327 } else if (this.reactHotLoaderTransformer) {
328 // This is a plain "export default E" statement and we need to assign E to a variable.
329 // Change "export default E" to "let _default; exports.default = _default = E"
330 const defaultVarName = this.nameManager.claimFreeName("_default");
331 this.tokens.replaceToken(`let ${defaultVarName}; exports.`);
332 this.tokens.copyToken();
333 this.tokens.appendCode(` = ${defaultVarName} =`);
334 this.reactHotLoaderTransformer.setExtractedDefaultExportName(defaultVarName);
335 } else {
336 // This is a plain "export default E" statement, no additional requirements.
337 // Change "export default E" to "exports.default = E"
338 this.tokens.replaceToken("exports.");
339 this.tokens.copyToken();
340 this.tokens.appendCode(" =");
341 }
342 }
343
344 /**
345 * Transform a declaration like `export var`, `export let`, or `export const`.
346 */
347 processExportVar() {
348 if (this.isSimpleExportVar()) {
349 this.processSimpleExportVar();
350 } else {
351 this.processComplexExportVar();
352 }
353 }
354
355 /**
356 * Determine if the export is of the form:
357 * export var/let/const [varName] = [expr];
358 * In other words, determine if function name inference might apply.
359 */
360 isSimpleExportVar() {
361 let tokenIndex = this.tokens.currentIndex();
362 // export
363 tokenIndex++;
364 // var/let/const
365 tokenIndex++;
366 if (!this.tokens.matches1AtIndex(tokenIndex, _types.TokenType.name)) {
367 return false;
368 }
369 tokenIndex++;
370 while (tokenIndex < this.tokens.tokens.length && this.tokens.tokens[tokenIndex].isType) {
371 tokenIndex++;
372 }
373 if (!this.tokens.matches1AtIndex(tokenIndex, _types.TokenType.eq)) {
374 return false;
375 }
376 return true;
377 }
378
379 /**
380 * Transform an `export var` declaration initializing a single variable.
381 *
382 * For example, this:
383 * export const f = () => {};
384 * becomes this:
385 * const f = () => {}; exports.f = f;
386 *
387 * The variable is unused (e.g. exports.f has the true value of the export).
388 * We need to produce an assignment of this form so that the function will
389 * have an inferred name of "f", which wouldn't happen in the more general
390 * case below.
391 */
392 processSimpleExportVar() {
393 // export
394 this.tokens.removeInitialToken();
395 // var/let/const
396 this.tokens.copyToken();
397 const varName = this.tokens.identifierName();
398 // x: number -> x
399 while (!this.tokens.matches1(_types.TokenType.eq)) {
400 this.rootTransformer.processToken();
401 }
402 const endIndex = this.tokens.currentToken().rhsEndIndex;
403 if (endIndex == null) {
404 throw new Error("Expected = token with an end index.");
405 }
406 while (this.tokens.currentIndex() < endIndex) {
407 this.rootTransformer.processToken();
408 }
409 this.tokens.appendCode(`; exports.${varName} = ${varName}`);
410 }
411
412 /**
413 * Transform normal declaration exports, including handling destructuring.
414 * For example, this:
415 * export const {x: [a = 2, b], c} = d;
416 * becomes this:
417 * ({x: [exports.a = 2, exports.b], c: exports.c} = d;)
418 */
419 processComplexExportVar() {
420 this.tokens.removeInitialToken();
421 this.tokens.removeToken();
422 const needsParens = this.tokens.matches1(_types.TokenType.braceL);
423 if (needsParens) {
424 this.tokens.appendCode("(");
425 }
426
427 let depth = 0;
428 while (true) {
429 if (
430 this.tokens.matches1(_types.TokenType.braceL) ||
431 this.tokens.matches1(_types.TokenType.dollarBraceL) ||
432 this.tokens.matches1(_types.TokenType.bracketL)
433 ) {
434 depth++;
435 this.tokens.copyToken();
436 } else if (this.tokens.matches1(_types.TokenType.braceR) || this.tokens.matches1(_types.TokenType.bracketR)) {
437 depth--;
438 this.tokens.copyToken();
439 } else if (
440 depth === 0 &&
441 !this.tokens.matches1(_types.TokenType.name) &&
442 !this.tokens.currentToken().isType
443 ) {
444 break;
445 } else if (this.tokens.matches1(_types.TokenType.eq)) {
446 // Default values might have assignments in the RHS that we want to ignore, so skip past
447 // them.
448 const endIndex = this.tokens.currentToken().rhsEndIndex;
449 if (endIndex == null) {
450 throw new Error("Expected = token with an end index.");
451 }
452 while (this.tokens.currentIndex() < endIndex) {
453 this.rootTransformer.processToken();
454 }
455 } else {
456 const token = this.tokens.currentToken();
457 if (_tokenizer.isDeclaration.call(void 0, token)) {
458 const name = this.tokens.identifierName();
459 let replacement = this.importProcessor.getIdentifierReplacement(name);
460 if (replacement === null) {
461 throw new Error(`Expected a replacement for ${name} in \`export var\` syntax.`);
462 }
463 if (_tokenizer.isObjectShorthandDeclaration.call(void 0, token)) {
464 replacement = `${name}: ${replacement}`;
465 }
466 this.tokens.replaceToken(replacement);
467 } else {
468 this.rootTransformer.processToken();
469 }
470 }
471 }
472
473 if (needsParens) {
474 // Seek to the end of the RHS.
475 const endIndex = this.tokens.currentToken().rhsEndIndex;
476 if (endIndex == null) {
477 throw new Error("Expected = token with an end index.");
478 }
479 while (this.tokens.currentIndex() < endIndex) {
480 this.rootTransformer.processToken();
481 }
482 this.tokens.appendCode(")");
483 }
484 }
485
486 /**
487 * Transform this:
488 * export function foo() {}
489 * into this:
490 * function foo() {} exports.foo = foo;
491 */
492 processExportFunction() {
493 this.tokens.replaceToken("");
494 const name = this.processNamedFunction();
495 this.tokens.appendCode(` exports.${name} = ${name};`);
496 }
497
498 /**
499 * Skip past a function with a name and return that name.
500 */
501 processNamedFunction() {
502 if (this.tokens.matches1(_types.TokenType._function)) {
503 this.tokens.copyToken();
504 } else if (this.tokens.matches2(_types.TokenType.name, _types.TokenType._function)) {
505 if (!this.tokens.matchesContextual(_keywords.ContextualKeyword._async)) {
506 throw new Error("Expected async keyword in function export.");
507 }
508 this.tokens.copyToken();
509 this.tokens.copyToken();
510 }
511 if (this.tokens.matches1(_types.TokenType.star)) {
512 this.tokens.copyToken();
513 }
514 if (!this.tokens.matches1(_types.TokenType.name)) {
515 throw new Error("Expected identifier for exported function name.");
516 }
517 const name = this.tokens.identifierName();
518 this.tokens.copyToken();
519 if (this.tokens.currentToken().isType) {
520 this.tokens.removeInitialToken();
521 while (this.tokens.currentToken().isType) {
522 this.tokens.removeToken();
523 }
524 }
525 this.tokens.copyExpectedToken(_types.TokenType.parenL);
526 this.rootTransformer.processBalancedCode();
527 this.tokens.copyExpectedToken(_types.TokenType.parenR);
528 this.rootTransformer.processPossibleTypeRange();
529 this.tokens.copyExpectedToken(_types.TokenType.braceL);
530 this.rootTransformer.processBalancedCode();
531 this.tokens.copyExpectedToken(_types.TokenType.braceR);
532 return name;
533 }
534
535 /**
536 * Transform this:
537 * export class A {}
538 * into this:
539 * class A {} exports.A = A;
540 */
541 processExportClass() {
542 this.tokens.removeInitialToken();
543 if (this.tokens.matches1(_types.TokenType._abstract)) {
544 this.tokens.removeToken();
545 }
546 const name = this.rootTransformer.processNamedClass();
547 this.tokens.appendCode(` exports.${name} = ${name};`);
548 }
549
550 /**
551 * Transform this:
552 * export {a, b as c};
553 * into this:
554 * exports.a = a; exports.c = b;
555 *
556 * OR
557 *
558 * Transform this:
559 * export {a, b as c} from './foo';
560 * into the pre-generated Object.defineProperty code from the ImportProcessor.
561 */
562 processExportBindings() {
563 this.tokens.removeInitialToken();
564 this.tokens.removeToken();
565
566 const exportStatements = [];
567 while (true) {
568 if (this.tokens.matches1(_types.TokenType.braceR)) {
569 this.tokens.removeToken();
570 break;
571 }
572
573 const localName = this.tokens.identifierName();
574 let exportedName;
575 this.tokens.removeToken();
576 if (this.tokens.matchesContextual(_keywords.ContextualKeyword._as)) {
577 this.tokens.removeToken();
578 exportedName = this.tokens.identifierName();
579 this.tokens.removeToken();
580 } else {
581 exportedName = localName;
582 }
583 const newLocalName = this.importProcessor.getIdentifierReplacement(localName);
584 exportStatements.push(`exports.${exportedName} = ${newLocalName || localName};`);
585
586 if (this.tokens.matches1(_types.TokenType.braceR)) {
587 this.tokens.removeToken();
588 break;
589 }
590 if (this.tokens.matches2(_types.TokenType.comma, _types.TokenType.braceR)) {
591 this.tokens.removeToken();
592 this.tokens.removeToken();
593 break;
594 } else if (this.tokens.matches1(_types.TokenType.comma)) {
595 this.tokens.removeToken();
596 } else {
597 throw new Error(`Unexpected token: ${JSON.stringify(this.tokens.currentToken())}`);
598 }
599 }
600
601 if (this.tokens.matchesContextual(_keywords.ContextualKeyword._from)) {
602 // This is an export...from, so throw away the normal named export code
603 // and use the Object.defineProperty code from ImportProcessor.
604 this.tokens.removeToken();
605 const path = this.tokens.stringValue();
606 this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
607 } else {
608 // This is a normal named export, so use that.
609 this.tokens.appendCode(exportStatements.join(" "));
610 }
611
612 if (this.tokens.matches1(_types.TokenType.semi)) {
613 this.tokens.removeToken();
614 }
615 }
616
617 processExportStar() {
618 this.tokens.removeInitialToken();
619 while (!this.tokens.matches1(_types.TokenType.string)) {
620 this.tokens.removeToken();
621 }
622 const path = this.tokens.stringValue();
623 this.tokens.replaceTokenTrimmingLeftWhitespace(this.importProcessor.claimImportCode(path));
624 if (this.tokens.matches1(_types.TokenType.semi)) {
625 this.tokens.removeToken();
626 }
627 }
628} exports.default = CJSImportTransformer;