Skip to content

Commit 2bbdc8e

Browse files
authored
Throw proper error when indirectly reexporting a recursive binding (#4472)
* Throw proper error when indirectly reexporting a recursive binding * Fix unrelated typo
1 parent e3bfe69 commit 2bbdc8e

4 files changed

Lines changed: 49 additions & 8 deletions

File tree

docs/05-plugin-development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ Use Rollup's internal acorn instance to parse code to an AST.
905905
906906
#### `this.resolve`
907907
908-
**Type:** `(source: string, importer?: string, options?: {skipSelf?: boolean, isEntry?: boolean, isEntry?: boolean, custom?: {[plugin: string]: any}}) => Promise<{id: string, external: boolean | "absolute", moduleSideEffects: boolean | 'no-treeshake', syntheticNamedExports: boolean | string, meta: {[plugin: string]: any}} | null>`
908+
**Type:** `(source: string, importer?: string, options?: {skipSelf?: boolean, isEntry?: boolean, custom?: {[plugin: string]: any}}) => Promise<{id: string, external: boolean | "absolute", moduleSideEffects: boolean | 'no-treeshake', syntheticNamedExports: boolean | string, meta: {[plugin: string]: any}} | null>`
909909
910910
Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses, and determine if an import should be external. If `null` is returned, the import could not be resolved by Rollup or any plugin but was not explicitly marked as external by the user. If an absolute external id is returned that should remain absolute in the output either via the [`makeAbsoluteExternalsRelative`](guide/en/#makeabsoluteexternalsrelative) option or by explicit plugin choice in the [`resolveId`](guide/en/#resolveid) hook, `external` will be `"absolute"` instead of `true`.
911911

src/Module.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ function getVariableForExportNameRecursive(
134134
target: Module | ExternalModule,
135135
name: string,
136136
importerForSideEffects: Module | undefined,
137-
isExportAllSearch: boolean,
137+
isExportAllSearch: boolean | undefined,
138138
searchedNamesAndModules = new Map<string, Set<Module | ExternalModule>>()
139139
): [variable: Variable | null, indirectExternal?: boolean] {
140140
const searchedModules = searchedNamesAndModules.get(name);
@@ -561,7 +561,10 @@ export default class Module {
561561
return [this.exportShimVariable];
562562
}
563563
const name = exportDeclaration.localName;
564-
const variable = this.traceVariable(name, importerForSideEffects)!;
564+
const variable = this.traceVariable(name, {
565+
importerForSideEffects,
566+
searchedNamesAndModules
567+
})!;
565568
if (importerForSideEffects) {
566569
getOrCreate(
567570
importerForSideEffects.sideEffectDependenciesByVariable,
@@ -793,7 +796,18 @@ export default class Module {
793796
};
794797
}
795798

796-
traceVariable(name: string, importerForSideEffects?: Module): Variable | null {
799+
traceVariable(
800+
name: string,
801+
{
802+
importerForSideEffects,
803+
isExportAllSearch,
804+
searchedNamesAndModules
805+
}: {
806+
importerForSideEffects?: Module;
807+
isExportAllSearch?: boolean;
808+
searchedNamesAndModules?: Map<string, Set<Module | ExternalModule>>;
809+
} = EMPTY_OBJECT
810+
): Variable | null {
797811
const localVariable = this.scope.variables.get(name);
798812
if (localVariable) {
799813
return localVariable;
@@ -807,9 +821,13 @@ export default class Module {
807821
return otherModule.namespace;
808822
}
809823

810-
const [declaration] = otherModule.getVariableForExportName(importDeclaration.name, {
811-
importerForSideEffects: importerForSideEffects || this
812-
});
824+
const [declaration] = getVariableForExportNameRecursive(
825+
otherModule,
826+
importDeclaration.name,
827+
importerForSideEffects || this,
828+
isExportAllSearch,
829+
searchedNamesAndModules
830+
);
813831

814832
if (!declaration) {
815833
return this.error(
@@ -1057,7 +1075,9 @@ export default class Module {
10571075
name,
10581076
importerForSideEffects,
10591077
true,
1060-
searchedNamesAndModules
1078+
// We are creating a copy to handle the case where the same binding is
1079+
// imported through different namespace reexports gracefully
1080+
copyNameToModulesMap(searchedNamesAndModules)
10611081
);
10621082

10631083
if (module instanceof ExternalModule || indirectExternal) {
@@ -1199,3 +1219,9 @@ function setAlternativeExporterIfCyclic(
11991219
}
12001220
}
12011221
}
1222+
1223+
const copyNameToModulesMap = (
1224+
searchedNamesAndModules?: Map<string, Set<Module | ExternalModule>>
1225+
): Map<string, Set<Module | ExternalModule>> | undefined =>
1226+
searchedNamesAndModules &&
1227+
new Map(Array.from(searchedNamesAndModules, ([name, modules]) => [name, new Set(modules)]));
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const path = require('path');
2+
const ID_MAIN = path.join(__dirname, 'main.js');
3+
4+
module.exports = {
5+
description: 'throws proper error for circular reexports',
6+
error: {
7+
code: 'CIRCULAR_REEXPORT',
8+
id: ID_MAIN,
9+
message: '"foo" cannot be exported from main.js as it is a reexport that references itself.',
10+
watchFiles: [ID_MAIN]
11+
}
12+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { foo } from './main.js';
2+
3+
export { foo };

0 commit comments

Comments
 (0)