From a7b1181fef65b69a7ae6cd5e59045d311470e70a Mon Sep 17 00:00:00 2001 From: magic-akari Date: Fri, 11 Mar 2022 03:13:55 +0800 Subject: [PATCH] Fix update expression for exported bigints (#14341) --- .../src/rewrite-live-references.ts | 76 ++++++++ .../babel-helper-simple-access/src/index.ts | 14 +- .../babel-helper-simple-access/test/index.js | 181 ++++++++++++++++++ .../test/package.json | 1 + .../test/fixtures/amd/remap/output.js | 5 +- .../test/fixtures/loose/remap/output.js | 5 +- .../src/index.ts | 22 ++- .../fixtures/interop-loose/remap/output.js | 5 +- .../test/fixtures/interop/remap/output.js | 5 +- .../misc/import-const-throw/output.js | 30 +-- .../fixtures/misc/module-exports/output.js | 8 +- .../fixtures/regression/4462-T7565/output.js | 2 +- .../update-expression/bigint/exec.mjs | 16 ++ .../update-expression/bigint/input.mjs | 16 ++ .../update-expression/bigint/options.json | 4 + .../update-expression/bigint/output.js | 21 ++ .../negative-suffix/output.js | 2 +- .../positive-suffix/output.js | 2 +- .../test/fixtures/loose/remap/output.js | 5 +- .../test/fixtures/umd/remap/output.js | 5 +- 20 files changed, 397 insertions(+), 28 deletions(-) create mode 100644 packages/babel-helper-simple-access/test/index.js create mode 100644 packages/babel-helper-simple-access/test/package.json create mode 100644 packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/exec.mjs create mode 100644 packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/input.mjs create mode 100644 packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/options.json create mode 100644 packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/output.js diff --git a/packages/babel-helper-module-transforms/src/rewrite-live-references.ts b/packages/babel-helper-module-transforms/src/rewrite-live-references.ts index ca7b3e36e531..9ba5d3063182 100644 --- a/packages/babel-helper-module-transforms/src/rewrite-live-references.ts +++ b/packages/babel-helper-module-transforms/src/rewrite-live-references.ts @@ -112,6 +112,7 @@ export default function rewriteLiveReferences( programPath, // NOTE(logan): The 'Array.from' calls are to make this code with in loose mode. new Set([...Array.from(imported.keys()), ...Array.from(exported.keys())]), + false, ); // Rewrite reads/writes from imports and exports to have the correct behavior. @@ -290,6 +291,81 @@ const rewriteReferencesVisitor: Visitor = { } }, + UpdateExpression(path) { + const { + scope, + seen, + imported, + exported, + requeueInParent, + buildImportReference, + } = this; + + if (seen.has(path.node)) return; + + seen.add(path.node); + + const arg = path.get("argument"); + + // No change needed + if (arg.isMemberExpression()) return; + + const update = path.node; + + if (arg.isIdentifier()) { + const localName = arg.node.name; + + // redeclared in this scope + if (scope.getBinding(localName) !== path.scope.getBinding(localName)) { + return; + } + + const exportedNames = exported.get(localName); + const importData = imported.get(localName); + + if (exportedNames?.length > 0 || importData) { + if (importData) { + path.replaceWith( + assignmentExpression( + update.operator[0] + "=", + buildImportReference(importData, arg.node), + buildImportThrow(localName), + ), + ); + } else if (update.prefix) { + // ++foo + // => exports.foo = ++foo + path.replaceWith( + buildBindingExportAssignmentExpression( + this.metadata, + exportedNames, + cloneNode(update), + ), + ); + } else { + // foo++ + // => (ref = i++, exports.i = i, ref) + const ref = scope.generateDeclaredUidIdentifier(localName); + + path.replaceWith( + sequenceExpression([ + assignmentExpression("=", cloneNode(ref), cloneNode(update)), + buildBindingExportAssignmentExpression( + this.metadata, + exportedNames, + identifier(localName), + ), + cloneNode(ref), + ]), + ); + } + } + } + + requeueInParent(path); + path.skip(); + }, + AssignmentExpression: { exit(path) { const { diff --git a/packages/babel-helper-simple-access/src/index.ts b/packages/babel-helper-simple-access/src/index.ts index 8b812002934d..cdb5880752e8 100644 --- a/packages/babel-helper-simple-access/src/index.ts +++ b/packages/babel-helper-simple-access/src/index.ts @@ -11,18 +11,28 @@ import { } from "@babel/types"; import type { NodePath } from "@babel/traverse"; -export default function simplifyAccess(path: NodePath, bindingNames) { +export default function simplifyAccess( + path: NodePath, + bindingNames, + // TODO(Babel 8): Remove this + includeUpdateExpression: boolean = true, +) { path.traverse(simpleAssignmentVisitor, { scope: path.scope, bindingNames, seen: new WeakSet(), + includeUpdateExpression, }); } const simpleAssignmentVisitor = { + // TODO(Babel 8): Remove UpdateExpression UpdateExpression: { exit(path) { - const { scope, bindingNames } = this; + const { scope, bindingNames, includeUpdateExpression } = this; + if (!includeUpdateExpression) { + return; + } const arg = path.get("argument"); if (!arg.isIdentifier()) return; diff --git a/packages/babel-helper-simple-access/test/index.js b/packages/babel-helper-simple-access/test/index.js new file mode 100644 index 000000000000..7514f1d7917b --- /dev/null +++ b/packages/babel-helper-simple-access/test/index.js @@ -0,0 +1,181 @@ +import * as babel from "@babel/core"; +import simplifyAccess from "../lib/index.js"; + +const plugin = (_api, options) => { + const { includeUpdateExpression, bindingNames } = options; + + return { + visitor: { + Program(path) { + simplifyAccess.default( + path, + new Set(bindingNames), + includeUpdateExpression, + ); + }, + }, + }; +}; + +it("simplifyAccess with default config", function () { + const code = ` + let a = foo++; + a = ++foo; + foo++; + ++foo; + ++a; + a++; + foo = a++; + foo = ++a; + + let b = bar--; + b = --bar; + bar--; + --bar; + --b; + b--; + bar = b--; + bar = --b; + + let c = baz += 1; + baz += 1; + c += 1; + + function f() { + let foo = 1; + let a = foo++; + a = ++foo; + foo++; + ++foo; + ++a; + a++; + foo = a++; + foo = ++a; + } +`; + + const output = babel.transformSync(code, { + configFile: false, + ast: false, + plugins: [[plugin, { bindingNames: ["foo", "bar", "baz"] }]], + }).code; + + expect(output).toMatchInlineSnapshot(` + "var _foo, _bar; + + let a = (_foo = +foo, foo = _foo + 1, _foo); + a = foo = +foo + 1; + foo = foo + 1; + foo = foo + 1; + ++a; + a++; + foo = a++; + foo = ++a; + let b = (_bar = +bar, bar = _bar - 1, _bar); + b = bar = +bar - 1; + bar = bar - 1; + bar = bar - 1; + --b; + b--; + bar = b--; + bar = --b; + let c = baz = baz + 1; + baz = baz + 1; + c += 1; + + function f() { + let foo = 1; + let a = foo++; + a = ++foo; + foo++; + ++foo; + ++a; + a++; + foo = a++; + foo = ++a; + }" + `); +}); + +it("simplifyAccess with includeUpdateExpression=false", function () { + const code = ` + let a = foo++; + a = ++foo; + foo++; + ++foo; + ++a; + a++; + foo = a++; + foo = ++a; + + let b = bar--; + b = --bar; + bar--; + --bar; + --b; + b--; + bar = b--; + bar = --b; + + let c = baz += 1; + baz += 1; + c += 1; + + function f() { + let foo = 1; + let a = foo++; + a = ++foo; + foo++; + ++foo; + ++a; + a++; + foo = a++; + foo = ++a; + } +`; + + const output = babel.transformSync(code, { + configFile: false, + ast: false, + plugins: [ + [ + plugin, + { includeUpdateExpression: false, bindingNames: ["foo", "bar", "baz"] }, + ], + ], + }).code; + + expect(output).toMatchInlineSnapshot(` + "let a = foo++; + a = ++foo; + foo++; + ++foo; + ++a; + a++; + foo = a++; + foo = ++a; + let b = bar--; + b = --bar; + bar--; + --bar; + --b; + b--; + bar = b--; + bar = --b; + let c = baz = baz + 1; + baz = baz + 1; + c += 1; + + function f() { + let foo = 1; + let a = foo++; + a = ++foo; + foo++; + ++foo; + ++a; + a++; + foo = a++; + foo = ++a; + }" +`); +}); diff --git a/packages/babel-helper-simple-access/test/package.json b/packages/babel-helper-simple-access/test/package.json new file mode 100644 index 000000000000..5ffd9800b97c --- /dev/null +++ b/packages/babel-helper-simple-access/test/package.json @@ -0,0 +1 @@ +{ "type": "module" } diff --git a/packages/babel-plugin-transform-modules-amd/test/fixtures/amd/remap/output.js b/packages/babel-plugin-transform-modules-amd/test/fixtures/amd/remap/output.js index ed299cfcd574..c42f3be2f9ca 100644 --- a/packages/babel-plugin-transform-modules-amd/test/fixtures/amd/remap/output.js +++ b/packages/babel-plugin-transform-modules-amd/test/fixtures/amd/remap/output.js @@ -5,10 +5,13 @@ define(["exports"], function (_exports) { value: true }); _exports.test = _exports.f = _exports.e = _exports.c = _exports.a = void 0; + + var _test; + var test = 2; _exports.test = test; _exports.test = test = 5; - _exports.test = test = test + 1; + _test = test++, _exports.test = test, _test; (function () { var test = 2; diff --git a/packages/babel-plugin-transform-modules-amd/test/fixtures/loose/remap/output.js b/packages/babel-plugin-transform-modules-amd/test/fixtures/loose/remap/output.js index 7da7e38d4ac4..11f2791703d0 100644 --- a/packages/babel-plugin-transform-modules-amd/test/fixtures/loose/remap/output.js +++ b/packages/babel-plugin-transform-modules-amd/test/fixtures/loose/remap/output.js @@ -3,10 +3,13 @@ define(["exports"], function (_exports) { _exports.__esModule = true; _exports.test = _exports.f = _exports.e = _exports.c = _exports.a = void 0; + + var _test; + var test = 2; _exports.test = test; _exports.test = test = 5; - _exports.test = test = test + 1; + _test = test++, _exports.test = test, _test; (function () { var test = 2; diff --git a/packages/babel-plugin-transform-modules-commonjs/src/index.ts b/packages/babel-plugin-transform-modules-commonjs/src/index.ts index e9a2c78ca52e..452da2203629 100644 --- a/packages/babel-plugin-transform-modules-commonjs/src/index.ts +++ b/packages/babel-plugin-transform-modules-commonjs/src/index.ts @@ -89,6 +89,26 @@ export default declare((api, options) => { path.replaceWith(getAssertion(localName)); }, + UpdateExpression(path) { + const arg = path.get("argument"); + const localName = arg.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; + + path.replaceWith( + t.assignmentExpression( + path.node.operator[0] + "=", + arg.node, + getAssertion(localName), + ), + ); + }, + AssignmentExpression(path) { const left = path.get("left"); if (left.isIdentifier()) { @@ -162,7 +182,7 @@ export default declare((api, options) => { // These objects are specific to CommonJS and are not available in // real ES6 implementations. if (!allowCommonJSExports) { - simplifyAccess(path, new Set(["module", "exports"])); + simplifyAccess(path, new Set(["module", "exports"]), false); path.traverse(moduleExportsVisitor, { scope: path.scope, }); diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop-loose/remap/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop-loose/remap/output.js index f313009c0a55..3c6fd2c99137 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop-loose/remap/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop-loose/remap/output.js @@ -2,10 +2,13 @@ exports.__esModule = true; exports.test = exports.f = exports.e = exports.c = exports.a = void 0; + +var _test; + var test = 2; exports.test = test; exports.test = test = 5; -exports.test = test = test + 1; +_test = test++, exports.test = test, _test; (function () { var test = 2; diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop/remap/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop/remap/output.js index f091718ee4ee..dae3c0498d0d 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop/remap/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/interop/remap/output.js @@ -4,10 +4,13 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.test = exports.f = exports.e = exports.c = exports.a = void 0; + +var _test; + var test = 2; exports.test = test; exports.test = test = 5; -exports.test = test = test + 1; +_test = test++, exports.test = test, _test; (function () { var test = 2; diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js index 79d37c0932bf..4656571763d3 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/import-const-throw/output.js @@ -74,24 +74,30 @@ Bar && (Bar = (4, function () { _baz.Baz && (_baz.Baz = (4, function () { throw new Error('"' + "Baz" + '" is read-only.'); }())); -_foo.default = (_foo.default - 1, function () { + +_foo.default -= function () { throw new Error('"' + "Foo" + '" is read-only.'); -}()); -Bar = (Bar - 1, function () { +}(); + +Bar -= function () { throw new Error('"' + "Bar" + '" is read-only.'); -}()); -_baz.Baz = (_baz.Baz - 1, function () { +}(); + +_baz.Baz -= function () { throw new Error('"' + "Baz" + '" is read-only.'); -}()); -_foo.default = (_foo.default + 1, function () { +}(); + +_foo.default += function () { throw new Error('"' + "Foo" + '" is read-only.'); -}()); -Bar = (Bar + 1, function () { +}(); + +Bar += function () { throw new Error('"' + "Bar" + '" is read-only.'); -}()); -_baz.Baz = (_baz.Baz + 1, function () { +}(); + +_baz.Baz += function () { throw new Error('"' + "Baz" + '" is read-only.'); -}()); +}(); for (let _Foo in {}) { (function () { diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/module-exports/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/module-exports/output.js index 3e9254c59bf2..93a06b3ff979 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/module-exports/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/misc/module-exports/output.js @@ -9,9 +9,9 @@ console.log(function () { throw new Error("The CommonJS '" + "exports" + "' variable is not available in ES6 modules." + "Consider setting setting sourceType:script or sourceType:unambiguous in your " + "Babel config for this file."); }().prop); -exports = function () { +exports += function () { throw new Error("The CommonJS '" + "exports" + "' variable is not available in ES6 modules." + "Consider setting setting sourceType:script or sourceType:unambiguous in your " + "Babel config for this file."); -}() + 1; +}(); exports = function () { throw new Error("The CommonJS '" + "exports" + "' variable is not available in ES6 modules." + "Consider setting setting sourceType:script or sourceType:unambiguous in your " + "Babel config for this file."); @@ -36,9 +36,9 @@ console.log(function () { throw new Error("The CommonJS '" + "module" + "' variable is not available in ES6 modules." + "Consider setting setting sourceType:script or sourceType:unambiguous in your " + "Babel config for this file."); }().exports); -module = function () { +module += function () { throw new Error("The CommonJS '" + "module" + "' variable is not available in ES6 modules." + "Consider setting setting sourceType:script or sourceType:unambiguous in your " + "Babel config for this file."); -}() + 1; +}(); module = function () { throw new Error("The CommonJS '" + "module" + "' variable is not available in ES6 modules." + "Consider setting setting sourceType:script or sourceType:unambiguous in your " + "Babel config for this file."); diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/regression/4462-T7565/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/regression/4462-T7565/output.js index ac7264e25d2f..cdd837001691 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/regression/4462-T7565/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/regression/4462-T7565/output.js @@ -9,5 +9,5 @@ var _yy; var yy = 0; exports.yy = yy; -var zz = (_yy = +yy, exports.yy = yy = _yy + 1, _yy); +var zz = (_yy = yy++, exports.yy = yy, _yy); exports.zz = zz; diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/exec.mjs b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/exec.mjs new file mode 100644 index 000000000000..25f3ff97e281 --- /dev/null +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/exec.mjs @@ -0,0 +1,16 @@ +export let foo = 1n; + +export let bar = foo++; + +export let baz = ++bar; + +expect(foo).toBe(2n); +expect(bar).toBe(2n); +expect(baz).toBe(2n); + +export { foo as foofoo, bar as barbar }; +export { baz as bazbaz }; + +--foo; +bar--; +baz--; diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/input.mjs b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/input.mjs new file mode 100644 index 000000000000..25f3ff97e281 --- /dev/null +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/input.mjs @@ -0,0 +1,16 @@ +export let foo = 1n; + +export let bar = foo++; + +export let baz = ++bar; + +expect(foo).toBe(2n); +expect(bar).toBe(2n); +expect(baz).toBe(2n); + +export { foo as foofoo, bar as barbar }; +export { baz as bazbaz }; + +--foo; +bar--; +baz--; diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/options.json b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/options.json new file mode 100644 index 000000000000..4e7f49bb66b3 --- /dev/null +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/options.json @@ -0,0 +1,4 @@ +{ + "externalHelpers": false, + "minNodeVersion": "10.0.0" +} diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/output.js new file mode 100644 index 000000000000..c38fbf00f82c --- /dev/null +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/bigint/output.js @@ -0,0 +1,21 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.foofoo = exports.foo = exports.bazbaz = exports.baz = exports.barbar = exports.bar = void 0; + +var _foo, _bar, _baz; + +let foo = 1n; +exports.foofoo = exports.foo = foo; +let bar = (_foo = foo++, exports.foofoo = exports.foo = foo, _foo); +exports.barbar = exports.bar = bar; +let baz = exports.barbar = exports.bar = ++bar; +exports.bazbaz = exports.baz = baz; +expect(foo).toBe(2n); +expect(bar).toBe(2n); +expect(baz).toBe(2n); +exports.foofoo = exports.foo = --foo; +_bar = bar--, exports.barbar = exports.bar = bar, _bar; +_baz = baz--, exports.bazbaz = exports.baz = baz, _baz; diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/negative-suffix/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/negative-suffix/output.js index e6ac92435834..c25e5f37f5f7 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/negative-suffix/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/negative-suffix/output.js @@ -9,7 +9,7 @@ let diffLevel = 0; exports.diffLevel = diffLevel; function diff() { - if (!(exports.diffLevel = diffLevel = +diffLevel - 1)) { + if (!(exports.diffLevel = --diffLevel)) { console.log("hey"); } } diff --git a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/positive-suffix/output.js b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/positive-suffix/output.js index b78cd08a8e10..4b20ce4c45a3 100644 --- a/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/positive-suffix/output.js +++ b/packages/babel-plugin-transform-modules-commonjs/test/fixtures/update-expression/positive-suffix/output.js @@ -9,7 +9,7 @@ let diffLevel = 0; exports.diffLevel = diffLevel; function diff() { - if (!(exports.diffLevel = diffLevel = +diffLevel + 1)) { + if (!(exports.diffLevel = ++diffLevel)) { console.log("hey"); } } diff --git a/packages/babel-plugin-transform-modules-umd/test/fixtures/loose/remap/output.js b/packages/babel-plugin-transform-modules-umd/test/fixtures/loose/remap/output.js index cf2cdf568afc..eefef7f4115c 100644 --- a/packages/babel-plugin-transform-modules-umd/test/fixtures/loose/remap/output.js +++ b/packages/babel-plugin-transform-modules-umd/test/fixtures/loose/remap/output.js @@ -15,10 +15,13 @@ _exports.__esModule = true; _exports.test = _exports.f = _exports.e = _exports.c = _exports.a = void 0; + + var _test; + var test = 2; _exports.test = test; _exports.test = test = 5; - _exports.test = test = test + 1; + _test = test++, _exports.test = test, _test; (function () { var test = 2; diff --git a/packages/babel-plugin-transform-modules-umd/test/fixtures/umd/remap/output.js b/packages/babel-plugin-transform-modules-umd/test/fixtures/umd/remap/output.js index cf882a053b9b..050ce7ed078e 100644 --- a/packages/babel-plugin-transform-modules-umd/test/fixtures/umd/remap/output.js +++ b/packages/babel-plugin-transform-modules-umd/test/fixtures/umd/remap/output.js @@ -17,10 +17,13 @@ value: true }); _exports.test = _exports.f = _exports.e = _exports.c = _exports.a = void 0; + + var _test; + var test = 2; _exports.test = test; _exports.test = test = 5; - _exports.test = test = test + 1; + _test = test++, _exports.test = test, _test; (function () { var test = 2;