@samitouri / QOS-React / commits / ca3d16c5ef

Enable hook syntax support

Updates the compiler to understand Flow hook syntax. Like component syntax, in infer mode hooks are compiled by default unless opted out. Looking ahead, i can imagine splitting up our compilation modes as follows: * Annotations: opt-in explicitly * Declarations: annotations + component/hook declarations * Infer: annotations, component/hook declarations, + component/hook-like functions This also suggest an alternative annotation strategy: "use react" (or "use component" / "use hook") as a general way to tell the compiler that a function is intended for React. Then opting out of memoization could do "use react(nomemo)".

Joe Savona committed Feb 17, 2024 at 17:31 UTC ca3d16c5eff02672fb818639716f7dd7c3f961c9
8 files changed +75 -14
compiler/packages/babel-plugin-react-forget/package.json
+1 -1
@@ -55,7 +55,7 @@
55 "babel-plugin-syntax-hermes-parser": "^0.15.1",
56 "eslint": "8.27.0",
57 "glob": "^7.1.6",
58 - "hermes-parser": "^0.18.2",
58 + "hermes-parser": "^0.19.1",
59 "jest": "^29.0.3",
60 "jest-environment-jsdom": "^29.0.3",
61 "prettier": "2.8.8",
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+4 -2
@@ -19,6 +19,7 @@ import {
19 } from "../HIR/Environment";
20 import { CodegenFunction } from "../ReactiveScopes";
21 import { isComponentDeclaration } from "../Utils/ComponentDeclaration";
22 +import { isHookDeclaration } from "../Utils/HookDeclaration";
23 import { assertExhaustive } from "../Utils/utils";
24 import { insertGatedFunctionDeclaration } from "./Gating";
25 import { addImportsToProgram, updateUseMemoCacheImport } from "./Imports";
@@ -408,8 +409,9 @@ export function shouldVisitNode(fn: BabelFn, pass: CompilerPass): boolean {
409 case "infer": {
410 const hookPattern = pass.opts.environment?.hookPattern ?? null;
411 return (
411 - // Component declarations are known components
412 - (fn.isFunctionDeclaration() && isComponentDeclaration(fn.node)) ||
412 + // Component and hook declarations are known components/hooks
413 + (fn.isFunctionDeclaration() &&
414 + (isComponentDeclaration(fn.node) || isHookDeclaration(fn.node))) ||
415 // Otherwise check if this is a component or hook-like function
416 isComponentOrHookLike(fn, hookPattern)
417 );
compiler/packages/babel-plugin-react-forget/src/Utils/HookDeclaration.ts new
+24
@@ -0,0 +1,24 @@
1 +/*
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import * as t from "@babel/types";
9 +
10 +export type HookDeclaration = t.FunctionDeclaration & {
11 + __hookDeclaration: boolean;
12 +};
13 +
14 +export function isHookDeclaration(
15 + node: t.FunctionDeclaration
16 +): node is HookDeclaration {
17 + return Object.prototype.hasOwnProperty.call(node, "__hookDeclaration");
18 +}
19 +
20 +export function parseHookDeclaration(
21 + node: t.FunctionDeclaration
22 +): HookDeclaration | null {
23 + return isHookDeclaration(node) ? node : null;
24 +}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md new
+30
@@ -0,0 +1,30 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @flow @compilationMode(infer)
6 +export default hook useFoo(bar: number) {
7 + return [bar];
8 +}
9 +
10 +```
11 +
12 +## Code
13 +
14 +```javascript
15 +import { unstable_useMemoCache as useMemoCache } from "react";
16 +function useFoo(bar) {
17 + const $ = useMemoCache(2);
18 + let t0;
19 + if ($[0] !== bar) {
20 + t0 = [bar];
21 + $[0] = bar;
22 + $[1] = t0;
23 + } else {
24 + t0 = $[1];
25 + }
26 + return t0;
27 +}
28 +
29 +```
30 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js new
+4
@@ -0,0 +1,4 @@
1 +// @flow @compilationMode(infer)
2 +export default hook useFoo(bar: number) {
3 + return [bar];
4 +}
compiler/packages/eslint-plugin-react-compiler/package.json
+1 -1
@@ -10,7 +10,7 @@
10 "@babel/core": "^7.19.1",
11 "@babel/plugin-proposal-private-methods": "^7.18.6",
12 "babel-plugin-react-forget": "*",
13 - "hermes-parser": "^0.17.1"
13 + "hermes-parser": "^0.19.1"
14 },
15 "devDependencies": {
16 "@babel/preset-env": "^7.22.4",
compiler/packages/sprout/src/SproutTodoFilter.ts
+1
@@ -420,6 +420,7 @@ const skipFilter = new Set([
420
421 // TODO: we should be able to support these
422 "component-declaration-basic.flow",
423 + "hook-declaration-basic.flow",
424 "nested-function-with-param-as-captured-dep",
425 "readonly-object-method-calls",
426 "readonly-object-method-calls-mutable-lambda",
compiler/yarn.lock
+10 -10
@@ -6410,10 +6410,10 @@ hermes-estree@0.17.1:
6410 resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.17.1.tgz#902806a900c185720424ffcf958027821d23c051"
6411 integrity sha512-EdUJms+eRE40OQxysFlPr1mPpvUbbMi7uDAKlScBw8o3tQY22BZ5yx56OYyp1bVaBm+7Cjc3NQz24sJEFXkPxg==
6412
6413 -hermes-estree@0.18.2:
6414 - version "0.18.2"
6415 - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.18.2.tgz#fd450fa1659cf074ceaa2ddeeb21674f3b2342f3"
6416 - integrity sha512-KoLsoWXJ5o81nit1wSyEZnWUGy9cBna9iYMZBR7skKh7okYAYKqQ9/OczwpMHn/cH0hKDyblulGsJ7FknlfVxQ==
6413 +hermes-estree@0.19.1:
6414 + version "0.19.1"
6415 + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.19.1.tgz#d5924f5fac2bf0532547ae9f506d6db8f3c96392"
6416 + integrity sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==
6417
6418 hermes-parser@0.14.0:
6419 version "0.14.0"
@@ -6429,19 +6429,19 @@ hermes-parser@0.15.1:
6429 dependencies:
6430 hermes-estree "0.15.1"
6431
6432 -hermes-parser@0.17.1, hermes-parser@^0.17.1:
6432 +hermes-parser@0.17.1:
6433 version "0.17.1"
6434 resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.17.1.tgz#8b5cbaff235fed28487812ad718f9c7182d0db0f"
6435 integrity sha512-yErtFLMEL6490fFJPurNn23OI2ciGAtaUfKUg9VPdcde9CmItCjOVQkJt1Xzawv5kuRzeIx0RE2E2Q9TbIgdzA==
6436 dependencies:
6437 hermes-estree "0.17.1"
6438
6439 -hermes-parser@^0.18.2:
6440 - version "0.18.2"
6441 - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.18.2.tgz#50f15e2fcd559a48c68cd7af259d4292298bd14d"
6442 - integrity sha512-1eQfvib+VPpgBZ2zYKQhpuOjw1tH+Emuib6QmjkJWJMhyjM8xnXMvA+76o9LhF0zOAJDZgPfQhg43cyXEyl5Ew==
6439 +hermes-parser@^0.19.1:
6440 + version "0.19.1"
6441 + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.19.1.tgz#1044348097165b7c93dc198a80b04ed5130d6b1a"
6442 + integrity sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==
6443 dependencies:
6444 - hermes-estree "0.18.2"
6444 + hermes-estree "0.19.1"
6445
6446 hmac-drbg@^1.0.1:
6447 version "1.0.1"