From 0ea295e83ba0b6eff8ff078830ae4da89a2aee00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 12 Sep 2017 19:41:24 -0700 Subject: [PATCH] Make sure that export * from does not overwrite named exports. --- packages/babel-helper-modules/src/index.js | 61 ++++++++++++++++++- .../src/normalize-and-load-metadata.js | 5 ++ .../fixtures/strict/export-all/expected.js | 10 +++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/packages/babel-helper-modules/src/index.js b/packages/babel-helper-modules/src/index.js index 899c2df8fc..3cd1ab33b7 100644 --- a/packages/babel-helper-modules/src/index.js +++ b/packages/babel-helper-modules/src/index.js @@ -49,6 +49,12 @@ export function rewriteModuleStatementsAndPrepareHeader( headers.push(buildESModuleHeader(meta, loose /* enumerable */)); } + const nameList = buildExportNameListDeclaration(path, meta); + if (nameList) { + meta.exportNameListName = nameList.name; + headers.push(nameList.statement); + } + // Create all of the statically known named exports. headers.push(...buildExportInitializationStatements(path, meta)); @@ -201,6 +207,8 @@ function buildESModuleHeader( const namespaceReexport = template(` Object.keys(NAMESPACE).forEach(function(key) { if (key === "default" || key === "__esModule") return; + VERIFY_NAME_LIST; + Object.defineProperty(EXPORTS, key, { enumerable: true, get: function() { @@ -209,15 +217,22 @@ const namespaceReexport = template(` }); }); `); +const buildNameListCheck = template(` + if (Object.prototype.hasOwnProperty.call(EXPORTS_LIST, key)) return; +`); /** * Create a re-export initialization loop for a specific imported namespace. */ function buildNamespaceReexport(metadata, namespace) { - // TODO: This should skip exporting a prop that is already exported. return namespaceReexport({ NAMESPACE: t.identifier(namespace), EXPORTS: t.identifier(metadata.exportName), + VERIFY_NAME_LIST: metadata.exportNameListName + ? buildNameListCheck({ + EXPORTS_LIST: t.identifier(metadata.exportNameListName), + }) + : null, }); } @@ -230,6 +245,48 @@ const reexportGetter = template(` }); `); +/** + * Build a statement declaring a variable that contains all of the exported + * variable names in an object so they can easily be referenced from an + * export * from statement to check for conflicts. + */ +function buildExportNameListDeclaration( + programPath: NodePath, + metadata: ModuleMetadata, +) { + const exportedVars = Object.create(null); + for (const data of metadata.local.values()) { + for (const name of data.names) { + exportedVars[name] = true; + } + } + + let hasReexport = false; + for (const data of metadata.source.values()) { + for (const exportName of data.reexports.keys()) { + exportedVars[exportName] = true; + } + for (const exportName of data.reexportNamespace) { + exportedVars[exportName] = true; + } + + hasReexport = hasReexport || data.reexportAll; + } + + if (!hasReexport || Object.keys(exportedVars).length === 0) return null; + + const name = programPath.scope.generateUidIdentifier("exportNames"); + + delete exportedVars.default; + + return { + name: name.name, + statement: t.variableDeclaration("var", [ + t.variableDeclarator(name, t.valueToNode(exportedVars)), + ]), + }; +} + /** * Create a set of statements that will initialize all of the statically-known * export names with their expected values. @@ -252,7 +309,7 @@ function buildExportInitializationStatements( exportNames.push(...data.names); } } - for (const [, data] of metadata.source) { + for (const data of metadata.source.values()) { for (const [exportName, importName] of data.reexports) { initStatements.push( reexportGetter({ diff --git a/packages/babel-helper-modules/src/normalize-and-load-metadata.js b/packages/babel-helper-modules/src/normalize-and-load-metadata.js index 70c5f38be9..be901fb12e 100644 --- a/packages/babel-helper-modules/src/normalize-and-load-metadata.js +++ b/packages/babel-helper-modules/src/normalize-and-load-metadata.js @@ -4,6 +4,10 @@ import * as t from "babel-types"; export type ModuleMetadata = { exportName: string, + + // The name of the variable that will reference an object containing export names. + exportNameListName: null | string, + // Lookup from local binding to export information. local: Map, @@ -107,6 +111,7 @@ export default function normalizeModuleAndLoadMetadata( return { exportName, + exportNameListName: null, local, source, }; diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-all/expected.js b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-all/expected.js index eae4298a85..443315cbad 100644 --- a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-all/expected.js +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/strict/export-all/expected.js @@ -3,6 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true }); +var _exportNames = { + z: true, + a: true, + b: true, + d: true, + e: true, + f: true, + c: true +}; exports.b = b; exports.default = _default; Object.defineProperty(exports, "c", { @@ -17,6 +26,7 @@ var _mod = require("mod"); Object.keys(_mod).forEach(function (key) { if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; Object.defineProperty(exports, key, { enumerable: true, get: function () {