@samitouri / QOS-React-2 / commits / 4f55a66d5c

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;