Skip to content

Commit

Permalink
Fix some additional enableLegacyBabel5ModuleInterop cases
Browse files Browse the repository at this point in the history
Follow-up from #804.
* When exporting `{T as default}`, treat it as a type-only export that doesn't
  count as a real default export.
* When explicitly doing `export default T`, treat it as a type-only export.
* Treat `export enum` as a named export.
  • Loading branch information
alangpierce committed Jul 11, 2023
1 parent 631f64c commit b3f6ea2
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 23 deletions.
24 changes: 17 additions & 7 deletions src/transformers/CJSImportTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import getDeclarationInfo, {
EMPTY_DECLARATION_INFO,
} from "../util/getDeclarationInfo";
import getImportExportSpecifierInfo from "../util/getImportExportSpecifierInfo";
import isExportFrom from "../util/isExportFrom";
import {removeMaybeImportAttributes} from "../util/removeMaybeImportAttributes";
import shouldElideDefaultExport from "../util/shouldElideDefaultExport";
import type ReactHotLoaderTransformer from "./ReactHotLoaderTransformer";
Expand Down Expand Up @@ -280,12 +281,13 @@ export default class CJSImportTransformer extends Transformer {
this.tokens.matches2(tt._export, tt._enum) ||
this.tokens.matches3(tt._export, tt._const, tt._enum)
) {
this.hadNamedExport = true;
// Let the TypeScript transform handle it.
return false;
}
if (this.tokens.matches2(tt._export, tt._default)) {
this.hadDefaultExport = true;
if (this.tokens.matches3(tt._export, tt._default, tt._enum)) {
this.hadDefaultExport = true;
// Flow export default enums need some special handling, so handle them
// in that tranform rather than this one.
return false;
Expand Down Expand Up @@ -488,6 +490,7 @@ export default class CJSImportTransformer extends Transformer {
}

private processExportDefault(): void {
let exportedRuntimeValue = true;
if (
this.tokens.matches4(tt._export, tt._default, tt._function, tt.name) ||
// export default async function
Expand Down Expand Up @@ -523,6 +526,7 @@ export default class CJSImportTransformer extends Transformer {
// If the exported value is just an identifier and should be elided by TypeScript
// rules, then remove it entirely. It will always have the form `export default e`,
// where `e` is an identifier.
exportedRuntimeValue = false;
this.tokens.removeInitialToken();
this.tokens.removeToken();
this.tokens.removeToken();
Expand All @@ -540,6 +544,9 @@ export default class CJSImportTransformer extends Transformer {
this.tokens.copyToken();
this.tokens.appendCode(" =");
}
if (exportedRuntimeValue) {
this.hadDefaultExport = true;
}
}

private copyDecorators(): void {
Expand Down Expand Up @@ -790,6 +797,8 @@ export default class CJSImportTransformer extends Transformer {
this.tokens.removeInitialToken();
this.tokens.removeToken();

const isReExport = isExportFrom(this.tokens);

const exportStatements: Array<string> = [];
while (true) {
if (this.tokens.matches1(tt.braceR)) {
Expand All @@ -803,18 +812,19 @@ export default class CJSImportTransformer extends Transformer {
this.tokens.removeToken();
}

if (!specifierInfo.isType) {
const shouldRemoveExport =
specifierInfo.isType ||
(!isReExport && this.shouldElideExportedIdentifier(specifierInfo.leftName));
if (!shouldRemoveExport) {
const exportedName = specifierInfo.rightName;
if (exportedName === "default") {
this.hadDefaultExport = true;
} else {
this.hadNamedExport = true;
}
if (!this.shouldElideExportedIdentifier(specifierInfo.leftName)) {
const localName = specifierInfo.leftName;
const newLocalName = this.importProcessor.getIdentifierReplacement(localName);
exportStatements.push(`exports.${exportedName} = ${newLocalName || localName};`);
}
const localName = specifierInfo.leftName;
const newLocalName = this.importProcessor.getIdentifierReplacement(localName);
exportStatements.push(`exports.${exportedName} = ${newLocalName || localName};`);
}

if (this.tokens.matches1(tt.braceR)) {
Expand Down
18 changes: 2 additions & 16 deletions src/transformers/ESMImportTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import getDeclarationInfo, {
} from "../util/getDeclarationInfo";
import getImportExportSpecifierInfo from "../util/getImportExportSpecifierInfo";
import {getNonTypeIdentifiers} from "../util/getNonTypeIdentifiers";
import isExportFrom from "../util/isExportFrom";
import {removeMaybeImportAttributes} from "../util/removeMaybeImportAttributes";
import shouldElideDefaultExport from "../util/shouldElideDefaultExport";
import type ReactHotLoaderTransformer from "./ReactHotLoaderTransformer";
Expand Down Expand Up @@ -330,7 +331,7 @@ export default class ESMImportTransformer extends Transformer {
this.tokens.copyExpectedToken(tt._export);
this.tokens.copyExpectedToken(tt.braceL);

const isReExport = this.isReExport();
const isReExport = isExportFrom(this.tokens);
let foundNonTypeExport = false;
while (!this.tokens.matches1(tt.braceR)) {
const specifierInfo = getImportExportSpecifierInfo(this.tokens);
Expand Down Expand Up @@ -369,21 +370,6 @@ export default class ESMImportTransformer extends Transformer {
return true;
}

/**
* Starting at `export {`, look ahead and return `true` if this is an
* `export {...} from` statement and `false` if this is a plain multi-export.
*/
private isReExport(): boolean {
let closeBraceIndex = this.tokens.currentIndex();
while (!this.tokens.matches1AtIndex(closeBraceIndex, tt.braceR)) {
closeBraceIndex++;
}
return (
this.tokens.matchesContextualAtIndex(closeBraceIndex + 1, ContextualKeyword._from) &&
this.tokens.matches1AtIndex(closeBraceIndex + 2, tt.string)
);
}

/**
* ESM elides all imports with the rule that we only elide if we see that it's
* a type and never see it as a value. This is in contrast to CJS, which
Expand Down
18 changes: 18 additions & 0 deletions src/util/isExportFrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {ContextualKeyword} from "../parser/tokenizer/keywords";
import {TokenType as tt} from "../parser/tokenizer/types";
import type TokenProcessor from "../TokenProcessor";

/**
* Starting at `export {`, look ahead and return `true` if this is an
* `export {...} from` statement and `false` if this is a plain multi-export.
*/
export default function isExportFrom(tokens: TokenProcessor): boolean {
let closeBraceIndex = tokens.currentIndex();
while (!tokens.matches1AtIndex(closeBraceIndex, tt.braceR)) {
closeBraceIndex++;
}
return (
tokens.matchesContextualAtIndex(closeBraceIndex + 1, ContextualKeyword._from) &&
tokens.matches1AtIndex(closeBraceIndex + 2, tt.string)
);
}
44 changes: 44 additions & 0 deletions test/imports-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,9 @@ module.exports = exports.default;
`,
{transforms: ["imports"], enableLegacyBabel5ModuleInterop: true},
);
});

it("properly treats `as default` as a default export when adding module exports suffix", () => {
assertResult(
`
export { x as default } from './foo'
Expand All @@ -788,6 +790,34 @@ module.exports = exports.default;
);
});

it("properly ignores regular default-exported types when deciding to add module exports suffix", () => {
assertResult(
`
type T = number;
export default T;
`,
`"use strict";${ESMODULE_PREFIX}
;
`,
{transforms: ["imports", "typescript"], enableLegacyBabel5ModuleInterop: true},
);
});

it("properly ignores `{T as default}` when deciding to add module exports suffix", () => {
assertResult(
`
type T = number;
export { T as default };
`,
`"use strict";${ESMODULE_PREFIX}
`,
{transforms: ["imports", "typescript"], enableLegacyBabel5ModuleInterop: true},
);
});

it("does not add module exports suffix when there is a named export", () => {
assertResult(
`
Expand All @@ -802,6 +832,20 @@ module.exports = exports.default;
);
});

it("properly treats exported TS enums as a named export when adding module exports suffix", () => {
assertResult(
`
export enum E {}
export default 4;
`,
`"use strict";${ESMODULE_PREFIX}
var E; (function (E) {})(E || (exports.E = E = {}));
exports. default = 4;
`,
{transforms: ["imports", "typescript"], enableLegacyBabel5ModuleInterop: true},
);
});

it("does not modify object keys matching import names", () => {
assertResult(
`
Expand Down

0 comments on commit b3f6ea2

Please sign in to comment.