@samitouri / QOS-React / commits / d6406d8360

Validate hook calls in object methods

Adds some test cases for hook calls in object methods. Initially we didn't catch these because InferTypes doesn't actually visit ObjectMethod bodies. Once we fix that we correctly reject these examples.

Joe Savona committed Feb 16, 2024 at 11:27 UTC d6406d8360c5d90fc777dfd1a720ba235c5a8a12
6 files changed +112 -1
compiler/packages/babel-plugin-react-forget/src/TypeInference/InferTypes.ts
+5 -1
@@ -69,7 +69,10 @@ function apply(func: HIRFunction, unifier: Unifier): void {
69 const { lvalue, value } = instr;
70 lvalue.identifier.type = unifier.get(lvalue.identifier.type);
71
72 - if (value.kind === "FunctionExpression") {
72 + if (
73 + value.kind === "FunctionExpression" ||
74 + value.kind === "ObjectMethod"
75 + ) {
76 apply(value.loweredFunc.func, unifier);
77 }
78 }
@@ -300,6 +303,7 @@ function* generateInstructionTypes(
303 }
304
305 case "ObjectMethod": {
306 + yield* generate(value.loweredFunc.func);
307 yield equation(left, { kind: "ObjectMethod" });
308 break;
309 }
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateHooksUsage.ts
+1
@@ -403,6 +403,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
403 for (const [, block] of fn.body.blocks) {
404 for (const instr of block.instructions) {
405 switch (instr.value.kind) {
406 + case "ObjectMethod":
407 case "FunctionExpression": {
408 visitFunctionExpression(errors, instr.value.loweredFunc.func);
409 break;
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md new
+39
@@ -0,0 +1,39 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @compilationMode(infer)
6 +function Component() {
7 + const f = () => {
8 + const x = {
9 + outer() {
10 + const g = () => {
11 + const y = {
12 + inner() {
13 + return useFoo();
14 + },
15 + };
16 + return y;
17 + };
18 + },
19 + };
20 + return x;
21 + };
22 +}
23 +
24 +```
25 +
26 +
27 +## Error
28 +
29 +```
30 + 7 | const y = {
31 + 8 | inner() {
32 +> 9 | return useFoo();
33 + | ^^^^^^ [ReactForget] InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (9:9)
34 + 10 | },
35 + 11 | };
36 + 12 | return y;
37 +```
38 +
39 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js new
+18
@@ -0,0 +1,18 @@
1 +// @compilationMode(infer)
2 +function Component() {
3 + const f = () => {
4 + const x = {
5 + outer() {
6 + const g = () => {
7 + const y = {
8 + inner() {
9 + return useFoo();
10 + },
11 + };
12 + return y;
13 + };
14 + },
15 + };
16 + return x;
17 + };
18 +}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @compilationMode(infer)
6 +function Component() {
7 + const x = {
8 + outer() {
9 + const y = {
10 + inner() {
11 + return useFoo();
12 + },
13 + };
14 + return y;
15 + },
16 + };
17 + return x;
18 +}
19 +
20 +```
21 +
22 +
23 +## Error
24 +
25 +```
26 + 5 | const y = {
27 + 6 | inner() {
28 +> 7 | return useFoo();
29 + | ^^^^^^ [ReactForget] InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (7:7)
30 + 8 | },
31 + 9 | };
32 + 10 | return y;
33 +```
34 +
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js new
+14
@@ -0,0 +1,14 @@
1 +// @compilationMode(infer)
2 +function Component() {
3 + const x = {
4 + outer() {
5 + const y = {
6 + inner() {
7 + return useFoo();
8 + },
9 + };
10 + return y;
11 + },
12 + };
13 + return x;
14 +}