Make sure that export * from does not overwrite named exports.

This commit is contained in:
Maël Nison
2017-09-12 19:41:24 -07:00
committed by Logan Smyth
parent 5bb6a83fa8
commit 0ea295e83b
3 changed files with 74 additions and 2 deletions

View File

@@ -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({

View File

@@ -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<string, LocalExportMetadata>,
@@ -107,6 +111,7 @@ export default function normalizeModuleAndLoadMetadata(
return {
exportName,
exportNameListName: null,
local,
source,
};

View File

@@ -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 () {