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()) { if (violation.isAssignmentExpression()) {
violation const { operator } = violation.node;
.get("right") if (operator === "=") {
.replaceWith( violation.replaceWith(
t.sequenceExpression([throwNode, violation.get("right").node]), 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()) { } else if (violation.isUpdateExpression()) {
violation.replaceWith( violation.replaceWith(
t.sequenceExpression([throwNode, violation.node]), t.sequenceExpression([
t.unaryExpression("+", violation.get("argument").node),
throwNode,
]),
); );
} else if (violation.isForXStatement()) { } else if (violation.isForXStatement()) {
violation.ensureBlock(); violation.ensureBlock();
violation
.get("left")
.replaceWith(
t.variableDeclaration("var", [
t.variableDeclarator(
violation.scope.generateUidIdentifier(name),
),
]),
);
violation.node.body.body.unshift(t.expressionStatement(throwNode)); 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 () { (function () {
var a = "foo"; var a = "foo";
if (false) a = (babelHelpers.readOnlyError("a"), "false"); if (false) "false", babelHelpers.readOnlyError("a");
return a; return a;
})(); })();

View File

@ -1,3 +1,3 @@
var a = 1, var a = 1,
b = 2; 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) { for (const i = 0; i < 3; i = i + 1) {
console.log(i); 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); 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; var a = 0;
function f() { function f() {
return (babelHelpers.readOnlyError("c"), ++c) + --a; return (+c, babelHelpers.readOnlyError("c")) + --a;
} }

View File

@ -1,4 +1,20 @@
const state1 = {};
expect(function() { expect(function() {
const a = 3; const a = 3;
state1.getA = () => a;
a = 7; a = 7;
}).toThrow('"a" is read-only'); }).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; const MULTIPLIER = 5;
MULTIPLIER = "overwrite"; MULTIPLIER = "overwrite";
const a = 5;
let b = 0;
a = b++;

View File

@ -1,2 +1,5 @@
var MULTIPLIER = 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) { function f(arr) {
const MULTIPLIER = 5; const MULTIPLIER = 5;
state.getMultiplier = () => MULTIPLIER;
for (MULTIPLIER in arr); for (MULTIPLIER in arr);
return 'survived'; return 'survived';
@ -8,5 +11,6 @@ function f(arr) {
expect(function() { expect(function() {
f([1,2,3]); f([1,2,3]);
}).toThrow('"MULTIPLIER" is read-only'); }).toThrow('"MULTIPLIER" is read-only');
expect(state.getMultiplier()).toBe(5); // Assignment did not succeed
expect(f([])).toBe('survived'); expect(f([])).toBe('survived');

View File

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

View File

@ -1,4 +1,23 @@
const state1 = {};
expect(function() { expect(function() {
const a = "str"; const a = "str";
state1.getA = () => a;
--a; --a;
}).toThrow('"a" is read-only'); }).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"; var a = "str";
babelHelpers.readOnlyError("a"), --a; +a, babelHelpers.readOnlyError("a");

View File

@ -1,4 +1,23 @@
const state1 = {};
expect(function() { expect(function() {
const foo = 1; const foo = 1;
state1.getFoo = () => foo;
foo++; foo++;
}).toThrow('"foo" is read-only'); }).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; var foo = 1;
babelHelpers.readOnlyError("foo"), foo++; +foo, babelHelpers.readOnlyError("foo");