Compile args to forwardRef/memo
Completes a todo (ie fixes a silly mistake) from a PR earlier in the stack, so we now correctly recognize and compile arguments to `React.forwardRef()` and `React.memo()`.
Joe Savona committed
Aug 29, 2023 at 22:09 UTC
4f55a66d5c643757e95fbe25b20b4fff2e1b16cb
8 files changed
+117
-15
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+21
-11
@@ -455,33 +455,43 @@ function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
455
*/
456
457
function isMemoCallback(path: NodePath<t.Expression>): boolean {
458
- return !!(
458
+ return (
459
path.parentPath.isCallExpression() &&
460
path.parentPath.get("callee").isExpression() &&
461
isReactFunction(path.parentPath.get("callee"), "memo")
462
);
463
}
464
465
+// Adapted from the ESLint rule at
466
+// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#L90-L103
467
function isReactFunctionLike(
468
node: NodePath<
469
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
470
>
471
): boolean {
472
const functionName = getFunctionName(node);
471
- if (functionName !== null) {
472
- if (!isComponentName(functionName) && !isHook(functionName)) {
473
+ // Check if the name is component or hook like:
474
+ if (
475
+ functionName !== null &&
476
+ (isComponentName(functionName) || isHook(functionName))
477
+ ) {
478
+ // As an added check we also look for hook invocations or JSX
479
+ return callsHooksOrCreatesJsx(node);
480
+ }
481
+ // Otherwise for function or arrow function expressions, check if they
482
+ // appear as the argument to React.forwardRef() or React.memo():
483
+ if (node.isFunctionExpression() || node.isArrowFunctionExpression()) {
484
+ if (isForwardRefCallback(node) || isMemoCallback(node)) {
485
+ // As an added check we also look for hook invocations or JSX
486
+ return callsHooksOrCreatesJsx(node);
487
+ } else {
488
return false;
489
}
475
- } else if (
476
- node.isExpression() &&
477
- !isForwardRefCallback(node) &&
478
- !isMemoCallback(node)
479
- ) {
480
- return false;
481
- } else {
482
- return false;
490
}
491
+ return false;
492
+}
493
494
+function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
495
let invokesHooks = false;
496
let createsJsx = false;
497
node.traverse({
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md
+10
-2
@@ -12,9 +12,17 @@ React.memo((props) => {
12
## Code
13
14
```javascript
15
-// @compilationMode(infer)
15
+import { unstable_useMemoCache as useMemoCache } from "react"; // @compilationMode(infer)
16
React.memo((props) => {
17
- return <div />;
17
+ const $ = useMemoCache(1);
18
+ let t0;
19
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
20
+ t0 = <div />;
21
+ $[0] = t0;
22
+ } else {
23
+ t0 = $[0];
24
+ }
25
+ return t0;
26
});
27
28
```
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-function-expression-React-memo-gating.expect.md
new
+38
@@ -0,0 +1,38 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @gating @compilationMode(infer)
6
+import React from "react";
7
+export default React.forwardRef(function notNamedLikeAComponent(props) {
8
+ return <div />;
9
+});
10
+
11
+```
12
+
13
+## Code
14
+
15
+```javascript
16
+import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
17
+import { unstable_useMemoCache as useMemoCache } from "react"; // @gating @compilationMode(infer)
18
+import React from "react";
19
+export default React.forwardRef(
20
+ isForgetEnabled_Fixtures()
21
+ ? function notNamedLikeAComponent(props) {
22
+ const $ = useMemoCache(1);
23
+ let t0;
24
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25
+ t0 = <div />;
26
+ $[0] = t0;
27
+ } else {
28
+ t0 = $[0];
29
+ }
30
+ return t0;
31
+ }
32
+ : function notNamedLikeAComponent(props) {
33
+ return <div />;
34
+ }
35
+);
36
+
37
+```
38
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-function-expression-React-memo-gating.js
new
+5
@@ -0,0 +1,5 @@
1
+// @gating @compilationMode(infer)
2
+import React from "react";
3
+export default React.forwardRef(function notNamedLikeAComponent(props) {
4
+ return <div />;
5
+});
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md
+10
-2
@@ -12,9 +12,17 @@ React.forwardRef((props) => {
12
## Code
13
14
```javascript
15
-// @compilationMode(infer)
15
+import { unstable_useMemoCache as useMemoCache } from "react"; // @compilationMode(infer)
16
React.forwardRef((props) => {
17
- return <div />;
17
+ const $ = useMemoCache(1);
18
+ let t0;
19
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
20
+ t0 = <div />;
21
+ $[0] = t0;
22
+ } else {
23
+ t0 = $[0];
24
+ }
25
+ return t0;
26
});
27
28
```
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md
new
+25
@@ -0,0 +1,25 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @compilationMode(infer)
6
+// This component is skipped bc it doesn't call any hooks or
7
+// use JSX:
8
+function Component(props) {
9
+ return render();
10
+}
11
+
12
+```
13
+
14
+## Code
15
+
16
+```javascript
17
+// @compilationMode(infer)
18
+// This component is skipped bc it doesn't call any hooks or
19
+// use JSX:
20
+function Component(props) {
21
+ return render();
22
+}
23
+
24
+```
25
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js
new
+6
@@ -0,0 +1,6 @@
1
+// @compilationMode(infer)
2
+// This component is skipped bc it doesn't call any hooks or
3
+// use JSX:
4
+function Component(props) {
5
+ return render();
6
+}
compiler/packages/sprout/src/SproutTodoFilter.ts
+2
@@ -448,6 +448,8 @@ const skipFilter = new Set([
448
"infer-functions-hook-with-hook-call",
449
"infer-functions-hook-with-jsx",
450
"infer-function-expression-component",
451
+ "infer-function-expression-React-memo-gating",
452
+ "infer-skip-components-without-hooks-or-jsx",
453
]);
454
455
export default skipFilter;