From b9800f09b7e8f398b2f58025175ea42a3d1e522a Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 6 Oct 2015 15:50:35 -0700 Subject: [PATCH] Handle export * reexport errors smartly --- src/compiler/checker.ts | 52 +++++++++++++------ src/compiler/diagnosticMessages.json | 4 ++ .../reference/exportStar-amd.errors.txt | 8 ++- .../baselines/reference/exportStar.errors.txt | 8 ++- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ec2a5bfb020..e4423773555 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1020,41 +1020,62 @@ namespace ts { return links.resolvedExports || (links.resolvedExports = getExportsForModule(moduleSymbol)); } - function extendExportSymbols(originalModule: Symbol, target: SymbolTable, source: SymbolTable) { + interface ExportStarDiagnosticsLookupTable { + [id: string]: {specifierText: string, exportsWithDuplicate: ExportDeclaration[]}; + } + + function extendExportSymbols(target: SymbolTable, source: SymbolTable, lookupTable?: ExportStarDiagnosticsLookupTable, exportNode?: ExportDeclaration) { for (let id in source) { if (id !== "default" && !hasProperty(target, id)) { target[id] = source[id]; + if (lookupTable && exportNode) { + lookupTable[id] = { + specifierText: getTextOfNode(exportNode.moduleSpecifier), + exportsWithDuplicate: [] + }; + } } - else if (id !== "default" && hasProperty(target, id)) { - diagnostics.add(createFileDiagnostic(getSourceFileOfNode(originalModule.valueDeclaration), 0, 0, Diagnostics.Duplicate_identifier_0, id)); + else if (lookupTable && exportNode && id !== "default" && hasProperty(target, id)) { + lookupTable[id].exportsWithDuplicate.push(exportNode); } } } function getExportsForModule(moduleSymbol: Symbol): SymbolTable { - let result: SymbolTable; let visitedSymbols: Symbol[] = []; - visit(moduleSymbol); - return result || moduleSymbol.exports; + return visit(moduleSymbol) || moduleSymbol.exports; // The ES6 spec permits export * declarations in a module to circularly reference the module itself. For example, // module 'a' can 'export * from "b"' and 'b' can 'export * from "a"' without error. - function visit(symbol: Symbol) { + function visit(symbol: Symbol): SymbolTable { if (symbol && symbol.flags & SymbolFlags.HasExports && !contains(visitedSymbols, symbol)) { visitedSymbols.push(symbol); - if (symbol !== moduleSymbol) { - if (!result) { - result = cloneSymbolTable(moduleSymbol.exports); - } - extendExportSymbols(moduleSymbol, result, symbol.exports); - } + let symbols: SymbolTable = cloneSymbolTable(symbol.exports); // All export * declarations are collected in an __export symbol by the binder let exportStars = symbol.exports["__export"]; if (exportStars) { + let nestedSymbols: SymbolTable = {}; + let lookupTable: ExportStarDiagnosticsLookupTable = {}; for (let node of exportStars.declarations) { - visit(resolveExternalModuleName(node, (node).moduleSpecifier)); + let resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier); + let exportedSymbols = visit(resolvedModule); + extendExportSymbols( + nestedSymbols, + exportedSymbols, + lookupTable, + node as ExportDeclaration + ); } + for (let id in lookupTable) { + if (id !== "export=" && lookupTable[id].exportsWithDuplicate.length && !(id in symbols)) { // It's not an error if the file with multiple export *'s with duplicate names exports a member with that name itself + for (let node of lookupTable[id].exportsWithDuplicate) { + diagnostics.add(createDiagnosticForNode(node, Diagnostics.An_export_Asterisk_from_0_declaration_has_already_exported_a_member_named_1_Consider_explicitly_re_exporting_to_resolve_the_ambiguity, lookupTable[id].specifierText, id)); + } + } + } + extendExportSymbols(symbols, nestedSymbols); } + return symbols; } } } @@ -13701,13 +13722,14 @@ namespace ts { function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { let moduleSymbol = getSymbolOfNode(node); - let links = getSymbolLinks(moduleSymbol); + let links: SymbolLinks = getSymbolLinks(moduleSymbol); if (!links.exportsChecked) { let exportEqualsSymbol = moduleSymbol.exports["export="]; if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { let declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); } + getExportsOfModule(moduleSymbol); // Checks for export * conflicts links.exportsChecked = true; } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f6656edb250..0f14ea0b021 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -832,6 +832,10 @@ "category": "Error", "code": 2307 }, + "An 'export * from {0}' declaration has already exported a member named '{1}'. Consider explicitly re-exporting to resolve the ambiguity.": { + "category": "Error", + "code": 2308 + }, "An export assignment cannot be used in a module with other exported elements.": { "category": "Error", "code": 2309 diff --git a/tests/baselines/reference/exportStar-amd.errors.txt b/tests/baselines/reference/exportStar-amd.errors.txt index adab0516103..237290485b0 100644 --- a/tests/baselines/reference/exportStar-amd.errors.txt +++ b/tests/baselines/reference/exportStar-amd.errors.txt @@ -1,4 +1,6 @@ tests/cases/conformance/es6/modules/main.ts(1,8): error TS1192: Module '"tests/cases/conformance/es6/modules/t4"' has no default export. +tests/cases/conformance/es6/modules/t4.ts(3,1): error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'x'. Consider explicitly re-exporting to resolve the ambiguity. +tests/cases/conformance/es6/modules/t4.ts(3,1): error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'y'. Consider explicitly re-exporting to resolve the ambiguity. ==== tests/cases/conformance/es6/modules/t1.ts (0 errors) ==== @@ -16,10 +18,14 @@ tests/cases/conformance/es6/modules/main.ts(1,8): error TS1192: Module '"tests/c var z = "z"; export { x, y, z }; -==== tests/cases/conformance/es6/modules/t4.ts (0 errors) ==== +==== tests/cases/conformance/es6/modules/t4.ts (2 errors) ==== export * from "./t1"; export * from "./t2"; export * from "./t3"; + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'x'. Consider explicitly re-exporting to resolve the ambiguity. + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'y'. Consider explicitly re-exporting to resolve the ambiguity. ==== tests/cases/conformance/es6/modules/main.ts (1 errors) ==== import hello, { x, y, z, foo } from "./t4"; diff --git a/tests/baselines/reference/exportStar.errors.txt b/tests/baselines/reference/exportStar.errors.txt index adab0516103..237290485b0 100644 --- a/tests/baselines/reference/exportStar.errors.txt +++ b/tests/baselines/reference/exportStar.errors.txt @@ -1,4 +1,6 @@ tests/cases/conformance/es6/modules/main.ts(1,8): error TS1192: Module '"tests/cases/conformance/es6/modules/t4"' has no default export. +tests/cases/conformance/es6/modules/t4.ts(3,1): error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'x'. Consider explicitly re-exporting to resolve the ambiguity. +tests/cases/conformance/es6/modules/t4.ts(3,1): error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'y'. Consider explicitly re-exporting to resolve the ambiguity. ==== tests/cases/conformance/es6/modules/t1.ts (0 errors) ==== @@ -16,10 +18,14 @@ tests/cases/conformance/es6/modules/main.ts(1,8): error TS1192: Module '"tests/c var z = "z"; export { x, y, z }; -==== tests/cases/conformance/es6/modules/t4.ts (0 errors) ==== +==== tests/cases/conformance/es6/modules/t4.ts (2 errors) ==== export * from "./t1"; export * from "./t2"; export * from "./t3"; + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'x'. Consider explicitly re-exporting to resolve the ambiguity. + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2308: An 'export * from "./t1"' declaration has already exported a member named 'y'. Consider explicitly re-exporting to resolve the ambiguity. ==== tests/cases/conformance/es6/modules/main.ts (1 errors) ==== import hello, { x, y, z, foo } from "./t4";