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