From b5a7fe4e1cd69e016934dabc754adb9f99f36859 Mon Sep 17 00:00:00 2001 From: Sathya Gunasekaran Date: Tue, 19 Mar 2024 17:06:17 +0000 Subject: [PATCH] [hir] Improve error message for mutating state --- .../src/HIR/Globals.ts | 1 + .../babel-plugin-react-forget/src/HIR/HIR.ts | 5 ++++ .../src/Inference/InferReferenceEffects.ts | 2 ++ ...pression-mutates-immutable-value.expect.md | 2 +- .../compiler/error.modify-state-2.expect.md | 29 +++++++++++++++++++ .../fixtures/compiler/error.modify-state-2.js | 8 +++++ .../compiler/error.modify-state.expect.md | 28 ++++++++++++++++++ .../fixtures/compiler/error.modify-state.js | 7 +++++ 8 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.js diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts index ba4a9140e0..49d97f5a79 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts @@ -261,6 +261,7 @@ const BUILTIN_HOOKS: Array<[string, BuiltInType]> = [ calleeEffect: Effect.Read, hookKind: "useState", returnValueKind: ValueKind.Frozen, + returnValueReason: ValueReason.State, }), ], [ diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts index 5a22aeb5f5..4e152e6974 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts @@ -1122,6 +1122,11 @@ export enum ValueReason { */ Context = "context", + /** + * A value returned from `useState` + */ + State = "state", + /** * Props of a component or arguments of a hook. */ diff --git a/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts index b91c75ca31..97d23ee340 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts @@ -1768,6 +1768,8 @@ function getWriteErrorReason(abstractValue: AbstractValue): string { return "Mutating a value returned from a function that should not be mutated."; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { return "Mutating props or hook arguments is not allowed. Consider using a local variable instead."; + } else if (abstractValue.reason.has(ValueReason.State)) { + return "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead."; } else { return "This mutates a variable that React considers immutable."; } diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md index 043a7443e4..5cb87f3652 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md @@ -21,7 +21,7 @@ function Component(props) { 3 | const onChange = (e) => { 4 | // INVALID! should use copy-on-write and pass the new value > 5 | x.value = e.target.value; - | ^^^^^^^ [ReactForget] InvalidReact: Mutating a value returned from a function that should not be mutated. (5:5) + | ^^^^^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (5:5) 6 | setX(x); 7 | }; 8 | return ; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.expect.md new file mode 100644 index 0000000000..d6849b8a02 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.expect.md @@ -0,0 +1,29 @@ + +## Input + +```javascript +import { useState } from "react"; + +function Foo() { + const [state, setState] = useState({ foo: { bar: 3 } }); + const foo = state.foo; + foo.bar = 1; + return state; +} + +``` + + +## Error + +``` + 4 | const [state, setState] = useState({ foo: { bar: 3 } }); + 5 | const foo = state.foo; +> 6 | foo.bar = 1; + | ^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (6:6) + 7 | return state; + 8 | } + 9 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.js new file mode 100644 index 0000000000..25a2782a06 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state-2.js @@ -0,0 +1,8 @@ +import { useState } from "react"; + +function Foo() { + const [state, setState] = useState({ foo: { bar: 3 } }); + const foo = state.foo; + foo.bar = 1; + return state; +} diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.expect.md new file mode 100644 index 0000000000..e25feccda0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.expect.md @@ -0,0 +1,28 @@ + +## Input + +```javascript +import { useState } from "react"; + +function Foo() { + let [state, setState] = useState({}); + state.foo = 1; + return state; +} + +``` + + +## Error + +``` + 3 | function Foo() { + 4 | let [state, setState] = useState({}); +> 5 | state.foo = 1; + | ^^^^^ [ReactForget] InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. (5:5) + 6 | return state; + 7 | } + 8 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.js new file mode 100644 index 0000000000..ef3ed87758 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.modify-state.js @@ -0,0 +1,7 @@ +import { useState } from "react"; + +function Foo() { + let [state, setState] = useState({}); + state.foo = 1; + return state; +}