Fix plugin-transform-block-scoping const violations (#13248)

* 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
This commit is contained in:
overlookmotel 2021-05-03 16:47:25 +01:00 committed by GitHub
parent fa01fbe052
commit f166b7ae58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 194 additions and 14 deletions

View File

@ -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));
}
}

View File

@ -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
}

View File

@ -0,0 +1,7 @@
const a = 5;
let b = 0;
a += b++;
a >>>= b++;
a ||= b++;
a &&= b++;
a ??= b++;

View File

@ -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"));

View File

@ -1,5 +1,5 @@
(function () {
var a = "foo";
if (false) a = (babelHelpers.readOnlyError("a"), "false");
if (false) "false", babelHelpers.readOnlyError("a");
return a;
})();

View File

@ -1,3 +1,3 @@
var a = 1,
b = 2;
a = (babelHelpers.readOnlyError("a"), 3);
3, babelHelpers.readOnlyError("a");

View File

@ -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);
}

View File

@ -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);
}

View File

@ -2,5 +2,5 @@ var c = 17;
var a = 0;
function f() {
return (babelHelpers.readOnlyError("c"), ++c) + --a;
return (+c, babelHelpers.readOnlyError("c")) + --a;
}

View File

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

View File

@ -1,3 +1,6 @@
const MULTIPLIER = 5;
MULTIPLIER = "overwrite";
const a = 5;
let b = 0;
a = b++;

View File

@ -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");

View File

@ -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');

View File

@ -1,6 +1,6 @@
var MULTIPLIER = 5;
for (MULTIPLIER in arr) {
for (var _MULTIPLIER in arr) {
babelHelpers.readOnlyError("MULTIPLIER");
;
}

View File

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

View File

@ -1,2 +1,2 @@
var a = "str";
babelHelpers.readOnlyError("a"), --a;
+a, babelHelpers.readOnlyError("a");

View File

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

View File

@ -1,2 +1,2 @@
var foo = 1;
babelHelpers.readOnlyError("foo"), foo++;
+foo, babelHelpers.readOnlyError("foo");