Skip to content

Commit

Permalink
Fix plugin-transform-block-scoping const violations (#13248)
Browse files Browse the repository at this point in the history
* Fix plugin-transform-block-scoping const violations

Fixes #13245

* Replace `a++` with `+a` where const violation

* Remove assignment where const violation

* Remove assignment for `&&=`, `||=`, `??=` where const violation

* Shorten test
  • Loading branch information
overlookmotel committed May 3, 2021
1 parent fa01fbe commit f166b7a
Show file tree
Hide file tree
Showing 18 changed files with 194 additions and 14 deletions.
42 changes: 37 additions & 5 deletions packages/babel-plugin-transform-block-scoping/src/index.js
Expand Up @@ -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));
}
}
Expand Down
@@ -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
}
@@ -0,0 +1,7 @@
const a = 5;
let b = 0;
a += b++;
a >>>= b++;
a ||= b++;
a &&= b++;
a ??= b++;
@@ -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"));
@@ -1,5 +1,5 @@
(function () {
var a = "foo";
if (false) a = (babelHelpers.readOnlyError("a"), "false");
if (false) "false", babelHelpers.readOnlyError("a");
return a;
})();
@@ -1,3 +1,3 @@
var a = 1,
b = 2;
a = (babelHelpers.readOnlyError("a"), 3);
3, babelHelpers.readOnlyError("a");
@@ -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);
}
@@ -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);
}
Expand Up @@ -2,5 +2,5 @@ var c = 17;
var a = 0;

function f() {
return (babelHelpers.readOnlyError("c"), ++c) + --a;
return (+c, babelHelpers.readOnlyError("c")) + --a;
}
@@ -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
@@ -1,3 +1,6 @@
const MULTIPLIER = 5;

MULTIPLIER = "overwrite";

const a = 5;
let b = 0;
a = b++;
@@ -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");
@@ -1,5 +1,8 @@
const state = {};
function f(arr) {
const MULTIPLIER = 5;
state.getMultiplier = () => MULTIPLIER;

for (MULTIPLIER in arr);

return 'survived';
Expand All @@ -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');
@@ -1,6 +1,6 @@
var MULTIPLIER = 5;

for (MULTIPLIER in arr) {
for (var _MULTIPLIER in arr) {
babelHelpers.readOnlyError("MULTIPLIER");
;
}
@@ -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
@@ -1,2 +1,2 @@
var a = "str";
babelHelpers.readOnlyError("a"), --a;
+a, babelHelpers.readOnlyError("a");
@@ -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
@@ -1,2 +1,2 @@
var foo = 1;
babelHelpers.readOnlyError("foo"), foo++;
+foo, babelHelpers.readOnlyError("foo");

0 comments on commit f166b7a

Please sign in to comment.