@samitouri / QOS-React-1 / commits / b5a7fe4e1c

[hir] Improve error message for mutating state

Sathya Gunasekaran committed Mar 19, 2024 at 17:06 UTC b5a7fe4e1cd69e016934dabc754adb9f99f36859
8 files changed +81 -1
compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts
+1
@@ -261,6 +261,7 @@ const BUILTIN_HOOKS: Array<[string, BuiltInType]> = [
261 calleeEffect: Effect.Read,
262 hookKind: "useState",
263 returnValueKind: ValueKind.Frozen,
264 + returnValueReason: ValueReason.State,
265 }),
266 ],
267 [
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+5
@@ -1122,6 +1122,11 @@ export enum ValueReason {
1122 */
1123 Context = "context",
1124
1125 + /**
1126 + * A value returned from `useState`
1127 + */
1128 + State = "state",
1129 +
1130 /**
1131 * Props of a component or arguments of a hook.
1132 */
compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+2
@@ -1768,6 +1768,8 @@ function getWriteErrorReason(abstractValue: AbstractValue): string {
1768 return "Mutating a value returned from a function that should not be mutated.";
1769 } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) {
1770 return "Mutating props or hook arguments is not allowed. Consider using a local variable instead.";
1771 + } else if (abstractValue.reason.has(ValueReason.State)) {
1772 + return "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead.";
1773 } else {
1774 return "This mutates a variable that React considers immutable.";
1775 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md
+1 -1
@@ -21,7 +21,7 @@ function Component(props) {
21 3 | const onChange = (e) => {
22 4 | // INVALID! should use copy-on-write and pass the new value
23 > 5 | x.value = e.target.value;
24 - | ^^^^^^^ [ReactForget] InvalidReact: Mutating a value returned from a function that should not be mutated. (5:5)
24 + | ^^^^^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (5:5)
25 6 | setX(x);
26 7 | };
27 8 | return <input value={x.value} onChange={onChange} />;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.expect.md new
+29
@@ -0,0 +1,29 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { useState } from "react";
6 +
7 +function Foo() {
8 + const [state, setState] = useState({ foo: { bar: 3 } });
9 + const foo = state.foo;
10 + foo.bar = 1;
11 + return state;
12 +}
13 +
14 +```
15 +
16 +
17 +## Error
18 +
19 +```
20 + 4 | const [state, setState] = useState({ foo: { bar: 3 } });
21 + 5 | const foo = state.foo;
22 +> 6 | foo.bar = 1;
23 + | ^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (6:6)
24 + 7 | return state;
25 + 8 | }
26 + 9 |
27 +```
28 +
29 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.js new
+8
@@ -0,0 +1,8 @@
1 +import { useState } from "react";
2 +
3 +function Foo() {
4 + const [state, setState] = useState({ foo: { bar: 3 } });
5 + const foo = state.foo;
6 + foo.bar = 1;
7 + return state;
8 +}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.expect.md new
+28
@@ -0,0 +1,28 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { useState } from "react";
6 +
7 +function Foo() {
8 + let [state, setState] = useState({});
9 + state.foo = 1;
10 + return state;
11 +}
12 +
13 +```
14 +
15 +
16 +## Error
17 +
18 +```
19 + 3 | function Foo() {
20 + 4 | let [state, setState] = useState({});
21 +> 5 | state.foo = 1;
22 + | ^^^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (5:5)
23 + 6 | return state;
24 + 7 | }
25 + 8 |
26 +```
27 +
28 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.js new
+7
@@ -0,0 +1,7 @@
1 +import { useState } from "react";
2 +
3 +function Foo() {
4 + let [state, setState] = useState({});
5 + state.foo = 1;
6 + return state;
7 +}