UNPKG

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