diff --git a/packages/babel-plugin-transform-block-scoping/src/index.js b/packages/babel-plugin-transform-block-scoping/src/index.js index 33b5ac8fb502..ae515895202f 100644 --- a/packages/babel-plugin-transform-block-scoping/src/index.js +++ b/packages/babel-plugin-transform-block-scoping/src/index.js @@ -433,17 +433,49 @@ class BlockScoping { ]); if (violation.isAssignmentExpression()) { - violation - .get("right") - .replaceWith( - t.sequenceExpression([throwNode, violation.get("right").node]), + const { operator } = violation.node; + if (operator === "=") { + violation.replaceWith( + t.sequenceExpression([violation.get("right").node, throwNode]), + ); + } else if (["&&=", "||=", "??="].includes(operator)) { + violation.replaceWith( + t.logicalExpression( + operator.slice(0, -1), + violation.get("left").node, + t.sequenceExpression([violation.get("right").node, throwNode]), + ), + ); + } else { + violation.replaceWith( + t.sequenceExpression([ + t.binaryExpression( + operator.slice(0, -1), + violation.get("left").node, + violation.get("right").node, + ), + throwNode, + ]), ); + } } else if (violation.isUpdateExpression()) { violation.replaceWith( - t.sequenceExpression([throwNode, violation.node]), + t.sequenceExpression([ + t.unaryExpression("+", violation.get("argument").node), + throwNode, + ]), ); } else if (violation.isForXStatement()) { violation.ensureBlock(); + violation + .get("left") + .replaceWith( + t.variableDeclaration("var", [ + t.variableDeclarator( + violation.scope.generateUidIdentifier(name), + ), + ]), + ); violation.node.body.body.unshift(t.expressionStatement(throwNode)); } } diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/exec.js new file mode 100644 index 000000000000..bec4bfac0f67 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/exec.js @@ -0,0 +1,62 @@ +const state1 = {}; +expect(function() { + const a = 3; + let b = 1; + state1.getA = () => a; + state1.getB = () => b; + + a += b++; +}).toThrow('"a" is read-only'); +expect(state1.getA()).toBe(3); // Assignment did not succeed +expect(state1.getB()).toBe(2); // `b++` was evaluated before error thrown + +const state2 = {}; +expect(function() { + const a = { + valueOf() { + state2.valueOfIsCalled = true; + } + }; + state2.a = a; + state2.getA = () => a; + state2.getB = () => b; + + let b = 1; + a += b++; +}).toThrow('"a" is read-only'); +expect(state2.getA()).toBe(state2.a); // Assignment did not succeed +expect(state2.getB()).toBe(2); // `b++` was evaluated before error thrown +expect(state2.valueOfIsCalled).toBe(true); // `a` was read before error thrown + +const state3 = {}; +expect(function() { + const a = 32; + let b = 1; + state3.getA = () => a; + state3.getB = () => b; + + a >>>= ++b; +}).toThrow('"a" is read-only'); +expect(state3.getA()).toBe(32); // Assignment did not succeed +expect(state3.getB()).toBe(2); // `++b` was evaluated before error thrown + +const state4 = {}; +expect(function() { + const a = 1; + let b = 1; + state4.getA = () => a; + state4.getB = () => b; + + a &&= ++b; +}).toThrow('"a" is read-only'); +expect(state4.getA()).toBe(1); // Assignment did not succeed +expect(state4.getB()).toBe(2); // `++b` was evaluated before error thrown + +{ + const a = 1; + let b = 1; + a ||= ++b; + + expect(a).toBe(1); // Assignment not made + expect(b).toBe(1); // `++b` was not evaluated +} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/input.js new file mode 100644 index 000000000000..96802f76f795 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/input.js @@ -0,0 +1,7 @@ +const a = 5; +let b = 0; +a += b++; +a >>>= b++; +a ||= b++; +a &&= b++; +a ??= b++; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/output.js new file mode 100644 index 000000000000..9bccb23e3d95 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/assignment-expression/output.js @@ -0,0 +1,7 @@ +var a = 5; +var b = 0; +a + b++, babelHelpers.readOnlyError("a"); +a >>> b++, babelHelpers.readOnlyError("a"); +a || (b++, babelHelpers.readOnlyError("a")); +a && (b++, babelHelpers.readOnlyError("a")); +a ?? (b++, babelHelpers.readOnlyError("a")); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/deadcode-violation/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/deadcode-violation/output.js index a9ecc00566a2..e1e5e16024cb 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/deadcode-violation/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/deadcode-violation/output.js @@ -1,5 +1,5 @@ (function () { var a = "foo"; - if (false) a = (babelHelpers.readOnlyError("a"), "false"); + if (false) "false", babelHelpers.readOnlyError("a"); return a; })(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/destructuring-assignment/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/destructuring-assignment/output.js index e05510513358..455758feeb71 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/destructuring-assignment/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/destructuring-assignment/output.js @@ -1,3 +1,3 @@ var a = 1, b = 2; -a = (babelHelpers.readOnlyError("a"), 3); +3, babelHelpers.readOnlyError("a"); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/input.js index 2bef733ec1e4..1f1545a8bb8d 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/input.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/input.js @@ -1,3 +1,7 @@ for (const i = 0; i < 3; i = i + 1) { console.log(i); } + +for (const j = 0; j < 3; j++) { + console.log(j); +} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/output.js index f1b882b2e441..e5f5014c1660 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/loop/output.js @@ -1,3 +1,7 @@ -for (var i = 0; i < 3; i = (babelHelpers.readOnlyError("i"), i + 1)) { +for (var i = 0; i < 3; i + 1, babelHelpers.readOnlyError("i")) { console.log(i); } + +for (var j = 0; j < 3; +j, babelHelpers.readOnlyError("j")) { + console.log(j); +} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/nested-update-expressions/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/nested-update-expressions/output.js index 4f5d9284d796..9dfbb9b9ee80 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/nested-update-expressions/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/nested-update-expressions/output.js @@ -2,5 +2,5 @@ var c = 17; var a = 0; function f() { - return (babelHelpers.readOnlyError("c"), ++c) + --a; + return (+c, babelHelpers.readOnlyError("c")) + --a; } diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/exec.js index f2094267f275..72169ff1cd52 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/exec.js @@ -1,4 +1,20 @@ +const state1 = {}; expect(function() { const a = 3; + state1.getA = () => a; + a = 7; }).toThrow('"a" is read-only'); +expect(state1.getA()).toBe(3); // Assignment did not succeed + +const state2 = {}; +expect(function() { + const a = 3; + let b = 0; + state2.getA = () => a; + state2.getB = () => b; + + a = b++; +}).toThrow('"a" is read-only'); +expect(state2.getA()).toBe(3); // Assignment did not succeed +expect(state2.getB()).toBe(1); // `b++` was evaluated before error thrown diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/input.js index 296ce9b558ff..19dd65d97b37 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/input.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/input.js @@ -1,3 +1,6 @@ const MULTIPLIER = 5; - MULTIPLIER = "overwrite"; + +const a = 5; +let b = 0; +a = b++; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/output.js index fe6f609dbd9c..0c6dfddc7fdd 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-assignment/output.js @@ -1,2 +1,5 @@ var MULTIPLIER = 5; -MULTIPLIER = (babelHelpers.readOnlyError("MULTIPLIER"), "overwrite"); +"overwrite", babelHelpers.readOnlyError("MULTIPLIER"); +var a = 5; +var b = 0; +b++, babelHelpers.readOnlyError("a"); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/exec.js index 0b4cd0db9ee3..088178e1f756 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/exec.js @@ -1,5 +1,8 @@ +const state = {}; function f(arr) { const MULTIPLIER = 5; + state.getMultiplier = () => MULTIPLIER; + for (MULTIPLIER in arr); return 'survived'; @@ -8,5 +11,6 @@ function f(arr) { expect(function() { f([1,2,3]); }).toThrow('"MULTIPLIER" is read-only'); +expect(state.getMultiplier()).toBe(5); // Assignment did not succeed expect(f([])).toBe('survived'); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/output.js index 71b72be5ca5a..b54d76d1f2dd 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/no-for-in/output.js @@ -1,6 +1,6 @@ var MULTIPLIER = 5; -for (MULTIPLIER in arr) { +for (var _MULTIPLIER in arr) { babelHelpers.readOnlyError("MULTIPLIER"); ; } diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/exec.js index 9b0f1ca2c216..6588bdea28e8 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/exec.js @@ -1,4 +1,23 @@ +const state1 = {}; expect(function() { const a = "str"; + state1.getA = () => a; + --a; }).toThrow('"a" is read-only'); +expect(state1.getA()).toBe("str"); // Assignment did not succeed + +const state2 = {}; +expect(function() { + const b = { + valueOf() { + state2.valueOfIsCalled = true; + } + }; + state2.b = b; + state2.getB = () => b; + + --b; +}).toThrow('"b" is read-only'); +expect(state2.getB()).toBe(state2.b); // Assignment did not succeed +expect(state2.valueOfIsCalled).toBe(true); // `bar` was read before error thrown diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/output.js index b9a3ee122b1d..fe840984434d 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression-prefix/output.js @@ -1,2 +1,2 @@ var a = "str"; -babelHelpers.readOnlyError("a"), --a; ++a, babelHelpers.readOnlyError("a"); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/exec.js index 3de129614e33..5734e67434e8 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/exec.js @@ -1,4 +1,23 @@ +const state1 = {}; expect(function() { const foo = 1; + state1.getFoo = () => foo; + foo++; }).toThrow('"foo" is read-only'); +expect(state1.getFoo()).toBe(1); // Assignment did not succeed + +const state2 = {}; +expect(function() { + const bar = { + valueOf() { + state2.valueOfIsCalled = true; + } + }; + state2.bar = bar; + state2.getBar = () => bar; + + bar++; +}).toThrow('"bar" is read-only'); +expect(state2.getBar()).toBe(state2.bar); // Assignment did not succeed +expect(state2.valueOfIsCalled).toBe(true); // `bar` was read before error thrown diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/output.js index 355150f7ec14..4f0f5202a7a9 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/output.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/const-violations/update-expression/output.js @@ -1,2 +1,2 @@ var foo = 1; -babelHelpers.readOnlyError("foo"), foo++; ++foo, babelHelpers.readOnlyError("foo");