From b79df60fe61c473975df1c6a7d8e28587636b093 Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Mon, 25 Sep 2017 16:14:10 -0700 Subject: [PATCH] Disallow usage of module/exports in ES6 modules. --- .../package.json | 1 + .../src/index.js | 89 ++++++++++++++++++- .../fixtures/misc/module-exports/actual.js | 22 +++++ .../fixtures/misc/module-exports/expected.js | 58 ++++++++++++ .../fixtures/misc/module-exports/options.json | 8 ++ 5 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/actual.js create mode 100644 packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/expected.js create mode 100644 packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/options.json diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/package.json b/packages/babel-plugin-transform-es2015-modules-commonjs/package.json index 7fc4ebda38..ee4d48148f 100644 --- a/packages/babel-plugin-transform-es2015-modules-commonjs/package.json +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/package.json @@ -6,6 +6,7 @@ "license": "MIT", "main": "lib/index.js", "dependencies": { + "babel-helper-simple-access": "7.0.0-beta.2", "babel-helper-module-transforms": "7.0.0-beta.2", "babel-types": "7.0.0-beta.2" }, diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js b/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js index b7119c8774..b51b288f38 100644 --- a/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/src/index.js @@ -6,8 +6,82 @@ import { ensureStatementsHoisted, wrapInterop, } from "babel-helper-module-transforms"; +import simplifyAccess from "babel-helper-simple-access"; + +export default function({ types: t, template }) { + const moduleAssertion = template(` + (function(){ + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); + })(); + `); + const exportsAssertion = template(` + (function(){ + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); + })(); + `); + const getAssertion = localName => + (localName === "module" ? moduleAssertion() : exportsAssertion()) + .expression; + + const moduleExportsVisitor = { + ReferencedIdentifier(path) { + const localName = path.node.name; + if (localName !== "module" && localName !== "exports") return; + + const localBinding = path.scope.getBinding(localName); + const rootBinding = this.scope.getBinding(localName); + + if ( + // redeclared in this scope + rootBinding !== localBinding || + (path.parentPath.isObjectProperty({ value: path.node }) && + path.parentPath.parentPath.isObjectPattern()) || + path.parentPath.isAssignmentExpression({ left: path.node }) || + path.isAssignmentExpression({ left: path.node }) + ) { + return; + } + + path.replaceWith(getAssertion(localName)); + }, + + AssignmentExpression(path) { + const left = path.get("left"); + if (left.isIdentifier()) { + const localName = path.node.name; + if (localName !== "module" && localName !== "exports") return; + + const localBinding = path.scope.getBinding(localName); + const rootBinding = this.scope.getBinding(localName); + + // redeclared in this scope + if (rootBinding !== localBinding) return; + + const right = path.get("right"); + right.replaceWith( + t.sequenceExpression([right.node, getAssertion(localName)]), + ); + } else if (left.isPattern()) { + const ids = left.getOuterBindingIdentifiers(); + const localName = Object.keys(ids).filter(localName => { + if (localName !== "module" && localName !== "exports") return false; + + return ( + this.scope.getBinding(localName) === + path.scope.getBinding(localName) + ); + })[0]; + + if (localName) { + const right = path.get("right"); + right.replaceWith( + t.sequenceExpression([right.node, getAssertion(localName)]), + ); + } + } + }, + }; -export default function({ types: t }) { return { visitor: { Program: { @@ -22,6 +96,9 @@ export default function({ types: t }) { strict, strictMode, noInterop, + + // Defaulting to 'true' for now. May change before 7.x major. + allowCommonJSExports = true, } = state.opts; // Rename the bindings auto-injected into the scope so there is no @@ -32,6 +109,16 @@ export default function({ types: t }) { path.scope.rename("__filename"); path.scope.rename("__dirname"); + // Rewrite references to 'module' and 'exports' to throw exceptions. + // These objects are specific to CommonJS and are not available in + // real ES6 implementations. + if (!allowCommonJSExports) { + simplifyAccess(path, new Set(["module", "exports"])); + path.traverse(moduleExportsVisitor, { + scope: path.scope, + }); + } + let moduleName = this.getModuleName(); if (moduleName) moduleName = t.stringLiteral(moduleName); diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/actual.js b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/actual.js new file mode 100644 index 0000000000..09eba756bc --- /dev/null +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/actual.js @@ -0,0 +1,22 @@ +import "foo"; + +console.log(exports); +console.log(exports.prop); +exports++; +exports += 4; +({ exports } = {}); +[ exports ] = []; +exports = {}; +exports.prop = ""; + + +console.log(module); +console.log(module.exports); +module++; +module += 4; +({ module } = {}); +[ module ] = []; +module = {}; +module.prop = ""; + + diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/expected.js b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/expected.js new file mode 100644 index 0000000000..f8aefc84ca --- /dev/null +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/expected.js @@ -0,0 +1,58 @@ +"use strict"; + +require("foo"); + +console.log(function () { + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); +}()); +console.log(function () { + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); +}().prop); + +exports = function () { + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); +}() + 1; + +exports = function () { + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); +}() + 4; + +({ + exports +} = ({}, function () { + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); +}())); +[exports] = ([], function () { + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); +}()); +exports = {}; +(function () { + throw new Error("The CommonJS 'exports' variable is not available in ES6 modules."); +})().prop = ""; +console.log(function () { + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); +}()); +console.log(function () { + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); +}().exports); + +module = function () { + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); +}() + 1; + +module = function () { + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); +}() + 4; + +({ + module +} = ({}, function () { + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); +}())); +[module] = ([], function () { + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); +}()); +module = {}; +(function () { + throw new Error("The CommonJS 'module' variable is not available in ES6 modules."); +})().prop = ""; diff --git a/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/options.json b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/options.json new file mode 100644 index 0000000000..b6fe2667b7 --- /dev/null +++ b/packages/babel-plugin-transform-es2015-modules-commonjs/test/fixtures/misc/module-exports/options.json @@ -0,0 +1,8 @@ +{ + "plugins": [ + [ + "transform-es2015-modules-commonjs", + { "allowCommonJSExports": false } + ] + ] +}