Compare commits

..

13 Commits

Author SHA1 Message Date
Lauren Tan
269325c04f [eprh] Try to fix tests
Summary:

Test Plan:

Reviewers:

Subscribers:

Tasks:

Tags:
2025-03-19 16:17:26 -04:00
Lauren Tan
829e8918a6 [eprh] Remove __EXPERIMENTAL__
`__EXPERIMENTAL__` flag doesn't have any use outside of the React repo. Let's remove these flags for now.
2025-03-19 16:17:26 -04:00
Lauren Tan
5644e81f27 [eprh] Move to compiler directory
Moves the plugin into the compiler directory.

- Remove eslint-plugin-react-hooks from bundles.js
- Remove eslint-plugin-react-hooks from ReactVersions.js
- Remove jest.config.js
- Remove babel.config-react-compiler.js
- Replace babel.config.js with copy from eslint-plugin-react-compiler
- Add tsup.config.ts to eslint-plugin-react-hooks
- Add eslint-plugin-react-hooks to compiler release scripts
2025-03-19 16:17:24 -04:00
lauren
208905257f [ci] Add cache cleanup workflow (#32675)
> Caches have branch scope restriction in place. This means that if
caches for a specific branch are using a lot of storage quota, it may
result into more frequently used caches from default branch getting
thrashed. For example, if there are many pull requests happening on a
repo and are creating caches, these cannot be used in default branch
scope but will still occupy a lot of space till they get cleaned up by
eviction policy. But sometime we want to clean them up on a faster
cadence so as to ensure default branch is not thrashing.


https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32675).
* __->__ #32675
* #32674
2025-03-19 15:42:04 -04:00
lauren
891a6332e3 [ci] Bump build_and_lint to 25 workers (#32674)
Increases number of workers for `build_and_lint`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32674).
* #32675
* __->__ #32674
2025-03-19 15:41:48 -04:00
lauren
a4842c92ea [ci] Centralize cache (#32672)
To avoid race conditions where multiple jobs try to write to the same
cache, we now centralize saving the cache and then reusing it in every
subsequent job.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32672).
* #32675
* #32674
* __->__ #32672
2025-03-19 15:25:07 -04:00
Niklas Holm
ada8bbbd6a [eslint-plugin-react-compiler] Fix type error with recommended config (#32666)
## Summary

In the recommended configuration for `eslint-plugin-react-compiler`,
i.e. `reactCompiler.configs.recommended`, the rule is typed as `string`
rather than `eslint.Linter.RuleEntry` or anything assignable thereto,
which results in the following type error if you type check your eslint
configuration:
```
Property ''react-compiler/react-compiler'' is incompatible with index signature.
  Type 'string' is not assignable to type 'RuleEntry | undefined'.
```
Simply adding a const assertion fixes the error.

## How did you test this change?

I emitted declarations for the module and confirmed that the rule is now
typed as the string literal `'error'`
2025-03-19 14:58:08 -04:00
lauren
e9c3b27b4b [ci] Bump all node_modules cache keys (#32671)
I'm seeing a lot of instances of

> Failed to save: Unable to reserve cache with key
runtime-and-compiler-node_modules-v5-X64-Linux-e454609794aae66da9909c77dd6efa073eceff7f44d6527611f8465e102578b4,
another job may be creating this cache.

which is adding ~20 seconds to every step. Let's try to bust the cache
following this
[comment](https://github.com/actions/cache/issues/485#issuecomment-744145040)
and see if that helps.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32671).
* #32672
* __->__ #32671
2025-03-19 14:42:45 -04:00
lauren
b0446ff06a [ci] Properly format commit message take 2 (#32673)
We need to use the commit message from `main`, not the builds branch
2025-03-19 14:01:49 -04:00
lauren
db7dfe0550 [eprh] Don't transpile to es5 (#32669)
Now that we've moved the sync location of the plugin, we no longer need
this since those restrictions no longer apply.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32669).
* __->__ #32669
* #32668
2025-03-19 11:49:42 -04:00
lauren
646835fb59 [ci] Properly format commit message (#32668)
Using the github variable for the commit message replaces the variable
inline. If the commit message contains quotes or other characters that
need to be escaped, this breaks the workflow.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32668).
* #32669
* __->__ #32668
2025-03-19 11:49:18 -04:00
Sebastian Markbåge
c2a1961747 Minor Fixes to View Transition Fixture (#32664)
Follow up to #32656.

Remove touchAction from SwipeRecognizer. I was under the wrong
impression that this was only the touch-action applied to this
particular element, but that parents would still win but in fact this
blocks the parent from scrolling in the other direction. By specifying a
fixed direction it also blocked rage-swiping in the other direction
early on.

Disable pointer-events on view-transition so that the scroll can be hit.
This means that touches hit below the items animating above. This allows
swiping to happen again before momentum scroll has finished. Previously
they were ignored. This only works as long as the SwipeRecognizer is
itself not animating. This means you can now rage-swipe in both
directions quickly.
2025-03-18 19:20:34 -04:00
mofeiZ
6584a6eec4 [compiler] Hoist dependencies from functions more conservatively (#32616)
Alternative to facebook/react#31584 which sets
enableTreatFunctionDepsAsConditional:true` by default.

This PR changes dependency hoisting to be more conservative while trying
to preserve an optimal "happy path". We assume that a function "is
likely called" if we observe the following in the react function body.

- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return

A function is also "likely called" if it is directly called, passed to
jsx / hooks, or returned from another function that "is likely called".

Note that this approach marks the function definition site with its
hoistable properties (not its use site). I tried implementing use-site
hoisting semantics, but it felt both unpredictable (i.e. as a developer,
I can't trust that callbacks are well memoized) and not helpful (type +
null checks of a value are usually colocated with their use site)

In this fixture (copied here for easy reference), it should be safe to
use `a.value` and `b.value` as dependencies, even though these functions
are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
  const logA = () => {
    console.log(a.value);
  };
  const logB = () => {
    console.log(b.value);
  };
  const hasLogged = useRef(false);
  const log = () => {
    if (!hasLogged.current) {
      logA();
      logB();
      hasLogged.current = true;
    }
  };
  return <Stringify log={log} shouldInvokeFns={true} />;
}
```

On the other hand, this means that we produce invalid output for code
like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
  const cb = e => arr2[0].value + e.value;
  const y = [];
  for (let i = 0; i < arr1.length; i++) {
    y.push(cb(arr1[i]));
  }
  return y;
}
```
2025-03-18 18:00:08 -04:00
94 changed files with 3421 additions and 788 deletions

View File

@@ -336,11 +336,11 @@ module.exports = {
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-timeline/**/*.js',
'packages/react-native-renderer/**/*.js',
'packages/eslint-plugin-react-hooks/**/*.js',
'packages/jest-react/**/*.js',
'packages/internal-test-utils/**/*.js',
'packages/**/__tests__/*.js',
'packages/**/npm/*.js',
'compiler/packages/eslint-plugin-react-hooks/**/*.js',
],
rules: {
'react-internal/prod-error-codes': OFF,
@@ -515,7 +515,7 @@ module.exports = {
},
},
{
files: ['packages/eslint-plugin-react-hooks/src/**/*'],
files: ['compiler/packages/eslint-plugin-react-hooks/src/**/*'],
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin'],

View File

@@ -38,7 +38,7 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: CI=true yarn test

View File

@@ -48,7 +48,7 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Publish packages to npm
run: |

View File

@@ -48,7 +48,7 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler lint
@@ -69,7 +69,7 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler jest
@@ -94,7 +94,7 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'

View File

@@ -31,7 +31,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -65,7 +65,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -123,7 +123,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
@@ -158,7 +158,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4

View File

@@ -17,6 +17,75 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
# ----- NODE_MODULES CACHE -----
# Centralize the node_modules cache so it is saved once and each subsequent job only needs to
# restore the cache. Prevents race conditions where multiple workflows try to write to the cache.
runtime_node_modules_cache:
name: Cache Runtime node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
runtime_compiler_node_modules_cache:
name: Cache Runtime, Compiler node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# ----- FLOW -----
discover_flow_inline_configs:
name: Discover flow inline configs
@@ -36,7 +105,7 @@ jobs:
flow:
name: Flow check ${{ matrix.flow_inline_config_shortname }}
needs: discover_flow_inline_configs
needs: [discover_flow_inline_configs, runtime_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -52,20 +121,22 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
# ----- FIZZ -----
check_generated_fizz_runtime:
name: Confirm generated inline Fizz runtime is up to date
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -77,15 +148,16 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: |
yarn generate-inline-fizz-runtime
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
@@ -93,6 +165,7 @@ jobs:
# ----- FEATURE FLAGS -----
flags:
name: Check flags
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -104,20 +177,22 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn flags
# ----- TESTS -----
test:
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -160,27 +235,30 @@ jobs:
yarn.lock
compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# yml is dumb. update the --total arg to yarn build if you change the number of workers
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
release_channel: [stable, experimental]
steps:
- uses: actions/checkout@v4
@@ -198,17 +276,19 @@ jobs:
distribution: temurin
java-version: 11.0.22
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn build --index=${{ matrix.worker_id }} --total=25 --r=${{ matrix.release_channel }} --ci
env:
CI: github
RELEASE_CHANNEL: ${{ matrix.release_channel }}
@@ -225,7 +305,7 @@ jobs:
test_build:
name: yarn test-build
needs: build_and_lint
needs: [build_and_lint, runtime_compiler_node_modules_cache]
strategy:
fail-fast: false
matrix:
@@ -276,16 +356,18 @@ jobs:
yarn.lock
compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -298,7 +380,7 @@ jobs:
process_artifacts_combined:
name: Process artifacts combined
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -310,15 +392,16 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -346,7 +429,7 @@ jobs:
check_error_codes:
name: Search build artifacts for unminified errors
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -358,15 +441,16 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -382,7 +466,7 @@ jobs:
check_release_dependencies:
name: Check release dependencies
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -394,15 +478,16 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -427,15 +512,16 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
id: node_modules
with:
path: |
**/node_modules
key: fixtures_dom-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn --cwd fixtures/dom install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -470,15 +556,16 @@ jobs:
# That means dependencies of the built packages are not installed.
# We need to install dependencies of the workroot to fulfill all dependency constraints
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
id: node_modules
with:
path: |
**/node_modules
key: fixtures_flight-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -517,7 +604,7 @@ jobs:
# ----- DEVTOOLS -----
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -533,15 +620,16 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -573,7 +661,7 @@ jobs:
run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: build_and_lint
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -585,15 +673,16 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -611,7 +700,7 @@ jobs:
sizebot:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
name: Run sizebot
needs: [build_and_lint]
needs: [build_and_lint, runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -623,15 +712,18 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
@@ -647,6 +739,7 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build for PR
uses: actions/download-artifact@v4
with:

View File

@@ -80,7 +80,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -246,15 +246,19 @@ jobs:
run: |
echo ":"
git status -u
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
git commit -m "${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})" || echo "No changes to commit"
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
@@ -413,15 +417,19 @@ jobs:
run: |
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
git commit -m "${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})" || echo "No changes to commit"
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push

View File

@@ -44,7 +44,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-eslint_e2e-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile

View File

@@ -41,7 +41,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile

View File

@@ -73,7 +73,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-release-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile

View File

@@ -0,0 +1,35 @@
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
name: (Shared) Cleanup Branch Caches
on:
pull_request:
types:
- closed
workflow_dispatch:
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
# `actions:write` permission is required to delete caches
# See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id
actions: write
contents: read
steps:
- name: Cleanup
run: |
echo "Fetching list of cache key"
cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id')
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh cache delete $cacheKey
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge

View File

@@ -30,7 +30,7 @@ jobs:
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -51,7 +51,7 @@ jobs:
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -72,7 +72,7 @@ jobs:
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -93,7 +93,7 @@ jobs:
with:
path: |
**/node_modules
key: shared-lint-node_modules-v5-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile

View File

@@ -33,7 +33,6 @@ const canaryChannelLabel = 'canary';
const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '5.2.0',
'jest-react': '0.17.0',
react: ReactVersion,
'react-art': ReactVersion,

View File

@@ -1,19 +0,0 @@
'use strict';
/**
* HACK: @poteto React Compiler inlines Zod in its build artifact. Zod spreads values passed to .map
* which causes issues in @babel/plugin-transform-spread in loose mode, as it will result in
* {undefined: undefined} which fails to parse.
*
* [@babel/plugin-transform-block-scoping', {throwIfClosureRequired: true}] also causes issues with
* the built version of the compiler. The minimal set of plugins needed for this file is reexported
* from babel.config-ts.
*
* I will remove this hack later when we move eslint-plugin-react-hooks into the compiler directory.
**/
const baseConfig = require('./babel.config-ts');
module.exports = {
plugins: baseConfig.plugins,
};

View File

@@ -16,7 +16,7 @@
"snap:build": "yarn workspace snap run build",
"snap:ci": "yarn snap:build && yarn snap",
"ts:analyze-trace": "scripts/ts-analyze-trace.sh",
"lint": "yarn eslint src",
"lint": "../../node_modules/eslint-v8/bin/eslint.js src",
"watch": "yarn build --watch"
},
"dependencies": {
@@ -43,7 +43,7 @@
"babel-jest": "^29.0.3",
"babel-plugin-fbt": "^1.0.0",
"babel-plugin-fbt-runtime": "^1.0.0",
"eslint": "^8.57.1",
"eslint-v8": "npm:eslint@^8.57.1",
"invariant": "^2.2.4",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",

View File

@@ -13,11 +13,13 @@ import {
BlockId,
DependencyPathEntry,
GeneratedSource,
getHookKind,
HIRFunction,
Identifier,
IdentifierId,
InstructionId,
InstructionValue,
LoweredFunction,
PropertyLiteral,
ReactiveScopeDependency,
ScopeId,
@@ -112,6 +114,9 @@ export function collectHoistablePropertyLoads(
hoistableFromOptionals,
registry,
nestedFnImmutableContext: null,
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
? new Set()
: getAssumedInvokedFunctions(fn),
});
}
@@ -127,6 +132,11 @@ type CollectHoistablePropertyLoadsContext = {
* but are currently kept separate for readability.
*/
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null;
/**
* Functions which are assumed to be eventually called (as opposed to ones which might
* not be called, e.g. the 0th argument of Array.map)
*/
assumedInvokedFns: ReadonlySet<LoweredFunction>;
};
function collectHoistablePropertyLoadsImpl(
fn: HIRFunction,
@@ -338,7 +348,13 @@ function collectNonNullsInBlocks(
context.registry.getOrCreateIdentifier(identifier),
);
}
const nodes = new Map<BlockId, BlockInfo>();
const nodes = new Map<
BlockId,
{
block: BasicBlock;
assumedNonNullObjects: Set<PropertyPathNode>;
}
>();
for (const [_, block] of fn.body.blocks) {
const assumedNonNullObjects = new Set<PropertyPathNode>(
knownNonNullIdentifiers,
@@ -358,32 +374,30 @@ function collectNonNullsInBlocks(
) {
assumedNonNullObjects.add(maybeNonNull);
}
if (
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod') &&
!fn.env.config.enableTreatFunctionDepsAsConditional
) {
if (instr.value.kind === 'FunctionExpression') {
const innerFn = instr.value.loweredFunc;
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
);
for (const entry of innerHoistables.assumedNonNullObjects) {
assumedNonNullObjects.add(entry);
if (context.assumedInvokedFns.has(innerFn)) {
const innerHoistableMap = collectHoistablePropertyLoadsImpl(
innerFn.func,
{
...context,
nestedFnImmutableContext:
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
.filter(place =>
isImmutableAtInstr(place.identifier, instr.id, context),
)
.map(place => place.identifier.id),
),
},
);
const innerHoistables = assertNonNull(
innerHoistableMap.get(innerFn.func.body.entry),
);
for (const entry of innerHoistables.assumedNonNullObjects) {
assumedNonNullObjects.add(entry);
}
}
}
}
@@ -591,3 +605,130 @@ function reduceMaybeOptionalChains(
}
} while (changed);
}
function getAssumedInvokedFunctions(
fn: HIRFunction,
temporaries: Map<
IdentifierId,
{fn: LoweredFunction; mayInvoke: Set<LoweredFunction>}
> = new Map(),
): ReadonlySet<LoweredFunction> {
const hoistableFunctions = new Set<LoweredFunction>();
/**
* Step 1: Conservatively collect identifier to function expression mappings
*/
for (const block of fn.body.blocks.values()) {
for (const {lvalue, value} of block.instructions) {
/**
* Conservatively only match function expressions which can have guaranteed ssa.
* ObjectMethods and ObjectProperties do not.
*/
if (value.kind === 'FunctionExpression') {
temporaries.set(lvalue.identifier.id, {
fn: value.loweredFunc,
mayInvoke: new Set(),
});
} else if (value.kind === 'StoreLocal') {
const lvalue = value.lvalue.place.identifier;
const maybeLoweredFunc = temporaries.get(value.value.identifier.id);
if (maybeLoweredFunc != null) {
temporaries.set(lvalue.id, maybeLoweredFunc);
}
} else if (value.kind === 'LoadLocal') {
const maybeLoweredFunc = temporaries.get(value.place.identifier.id);
if (maybeLoweredFunc != null) {
temporaries.set(lvalue.identifier.id, maybeLoweredFunc);
}
}
}
}
/**
* Step 2: Forward pass to do analysis of assumed function calls. Note that
* this is conservative and does not count indirect references through
* containers (e.g. `return {cb: () => {...}})`).
*/
for (const block of fn.body.blocks.values()) {
for (const {lvalue, value} of block.instructions) {
if (value.kind === 'CallExpression') {
const callee = value.callee;
const maybeHook = getHookKind(fn.env, callee.identifier);
const maybeLoweredFunc = temporaries.get(callee.identifier.id);
if (maybeLoweredFunc != null) {
// Direct calls
hoistableFunctions.add(maybeLoweredFunc.fn);
} else if (maybeHook != null) {
/**
* Assume arguments to all hooks are safe to invoke
*/
for (const arg of value.args) {
if (arg.kind === 'Identifier') {
const maybeLoweredFunc = temporaries.get(arg.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
}
}
} else if (value.kind === 'JsxExpression') {
/**
* Assume JSX attributes and children are safe to invoke
*/
for (const attr of value.props) {
if (attr.kind === 'JsxSpreadAttribute') {
continue;
}
const maybeLoweredFunc = temporaries.get(attr.place.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
for (const child of value.children ?? []) {
const maybeLoweredFunc = temporaries.get(child.identifier.id);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
} else if (value.kind === 'FunctionExpression') {
/**
* Recursively traverse into other function expressions which may invoke
* or pass already declared functions to react (e.g. as JSXAttributes).
*
* If lambda A calls lambda B, we assume lambda B is safe to invoke if
* lambda A is -- even if lambda B is conditionally called. (see
* `conditional-call-chain` fixture for example).
*/
const loweredFunc = value.loweredFunc.func;
const lambdasCalled = getAssumedInvokedFunctions(
loweredFunc,
temporaries,
);
const maybeLoweredFunc = temporaries.get(lvalue.identifier.id);
if (maybeLoweredFunc != null) {
for (const called of lambdasCalled) {
maybeLoweredFunc.mayInvoke.add(called);
}
}
}
}
if (block.terminal.kind === 'return') {
/**
* Assume directly returned functions are safe to call
*/
const maybeLoweredFunc = temporaries.get(
block.terminal.value.identifier.id,
);
if (maybeLoweredFunc != null) {
hoistableFunctions.add(maybeLoweredFunc.fn);
}
}
}
for (const [_, {fn, mayInvoke}] of temporaries) {
if (hoistableFunctions.has(fn)) {
for (const called of mayInvoke) {
hoistableFunctions.add(called);
}
}
}
return hoistableFunctions;
}

View File

@@ -23,11 +23,11 @@ import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(4);
let t0;
if ($[0] !== props.name) {
if ($[0] !== props) {
t0 = function () {
return <div>{props.name}</div>;
};
$[0] = props.name;
$[0] = props;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -0,0 +1,133 @@
## Input
```javascript
import {Stringify} from 'shared-runtime';
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const getArrMap1 = () => arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const getArrMap2 = () => arr1.map(cb2);
return (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo(t0) {
const $ = _c(13);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const cb1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = () => arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
} else {
t2 = $[4];
}
const getArrMap1 = t2;
let t3;
if ($[5] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[5] = arr2;
$[6] = t3;
} else {
t3 = $[6];
}
const cb2 = t3;
let t4;
if ($[7] !== arr1 || $[8] !== cb2) {
t4 = () => arr1.map(cb2);
$[7] = arr1;
$[8] = cb2;
$[9] = t4;
} else {
t4 = $[9];
}
const getArrMap2 = t4;
let t5;
if ($[10] !== getArrMap1 || $[11] !== getArrMap2) {
t5 = (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
$[10] = getArrMap1;
$[11] = getArrMap2;
$[12] = t5;
} else {
t5 = $[12];
}
return t5;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) <div>{"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}</div>
<div>{"getArrMap1":{"kind":"Function","result":[]},"getArrMap2":{"kind":"Function","result":[]},"shouldInvokeFns":true}</div>
<div>{"getArrMap1":{"kind":"Function","result":[2,3]},"getArrMap2":{"kind":"Function","result":[0,1]},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,35 @@
import {Stringify} from 'shared-runtime';
/**
* Forked from array-map-simple.js
*
* Named lambdas (e.g. cb1) may be defined in the top scope of a function and
* used in a different lambda (getArrMap1).
*
* Here, we should try to determine if cb1 is actually called. In this case:
* - getArrMap1 is assumed to be called as it's passed to JSX
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const getArrMap1 = () => arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const getArrMap2 = () => arr1.map(cb2);
return (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -0,0 +1,108 @@
## Input
```javascript
/**
* Forked from array-map-simple.js
*
* Whether lambdas are named or passed inline shouldn't affect whether we expect
* it to be called.
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Forked from array-map-simple.js
*
* Whether lambdas are named or passed inline shouldn't affect whether we expect
* it to be called.
*/
function useFoo(t0) {
const $ = _c(13);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const cb1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
} else {
t2 = $[4];
}
const x = t2;
let t3;
if ($[5] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[5] = arr2;
$[6] = t3;
} else {
t3 = $[6];
}
const cb2 = t3;
let t4;
if ($[7] !== arr1 || $[8] !== cb2) {
t4 = arr1.map(cb2);
$[7] = arr1;
$[8] = cb2;
$[9] = t4;
} else {
t4 = $[9];
}
const y = t4;
let t5;
if ($[10] !== x || $[11] !== y) {
t5 = [x, y];
$[10] = x;
$[11] = y;
$[12] = t5;
} else {
t5 = $[12];
}
return t5;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) [[],[]]
[[],[]]
[[2,3],[0,1]]

View File

@@ -0,0 +1,23 @@
/**
* Forked from array-map-simple.js
*
* Whether lambdas are named or passed inline shouldn't affect whether we expect
* it to be called.
*/
function useFoo({arr1, arr2}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
const cb2 = e => arr2[0].value + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -0,0 +1,130 @@
## Input
```javascript
/**
* Forked from array-map-simple.js
*
* Here, getVal1 has a known callsite in `cb1`, but `cb1` isn't known to be
* called (it's only passed to array.map). In this case, we should be
* conservative and assume that all named lambdas are conditionally called.
*/
function useFoo({arr1, arr2}) {
const getVal1 = () => arr1[0].value;
const cb1 = e => getVal1() + e.value;
const x = arr1.map(cb1);
const getVal2 = () => arr2[0].value;
const cb2 = e => getVal2() + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Forked from array-map-simple.js
*
* Here, getVal1 has a known callsite in `cb1`, but `cb1` isn't known to be
* called (it's only passed to array.map). In this case, we should be
* conservative and assume that all named lambdas are conditionally called.
*/
function useFoo(t0) {
const $ = _c(17);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = () => arr1[0].value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const getVal1 = t1;
let t2;
if ($[2] !== getVal1) {
t2 = (e) => getVal1() + e.value;
$[2] = getVal1;
$[3] = t2;
} else {
t2 = $[3];
}
const cb1 = t2;
let t3;
if ($[4] !== arr1 || $[5] !== cb1) {
t3 = arr1.map(cb1);
$[4] = arr1;
$[5] = cb1;
$[6] = t3;
} else {
t3 = $[6];
}
const x = t3;
let t4;
if ($[7] !== arr2) {
t4 = () => arr2[0].value;
$[7] = arr2;
$[8] = t4;
} else {
t4 = $[8];
}
const getVal2 = t4;
let t5;
if ($[9] !== getVal2) {
t5 = (e_0) => getVal2() + e_0.value;
$[9] = getVal2;
$[10] = t5;
} else {
t5 = $[10];
}
const cb2 = t5;
let t6;
if ($[11] !== arr1 || $[12] !== cb2) {
t6 = arr1.map(cb2);
$[11] = arr1;
$[12] = cb2;
$[13] = t6;
} else {
t6 = $[13];
}
const y = t6;
let t7;
if ($[14] !== x || $[15] !== y) {
t7 = [x, y];
$[14] = x;
$[15] = y;
$[16] = t7;
} else {
t7 = $[16];
}
return t7;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) [[],[]]
[[],[]]
[[2,3],[0,1]]

View File

@@ -0,0 +1,26 @@
/**
* Forked from array-map-simple.js
*
* Here, getVal1 has a known callsite in `cb1`, but `cb1` isn't known to be
* called (it's only passed to array.map). In this case, we should be
* conservative and assume that all named lambdas are conditionally called.
*/
function useFoo({arr1, arr2}) {
const getVal1 = () => arr1[0].value;
const cb1 = e => getVal1() + e.value;
const x = arr1.map(cb1);
const getVal2 = () => arr2[0].value;
const cb2 = e => getVal2() + e.value;
const y = arr1.map(cb2);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -0,0 +1,111 @@
## Input
```javascript
/**
* Test that we're not hoisting property reads from lambdas that are created to
* pass to opaque functions, which often have maybe-invoke semantics.
*
* In this example, we shouldn't hoist `arr[0].value` out of the lambda.
* ```js
* e => arr[0].value + e.value <-- created to pass to map
* arr.map(<cb>) <-- argument only invoked if array is non-empty
* ```
*/
function useFoo({arr1, arr2}) {
const x = arr1.map(e => arr1[0].value + e.value);
const y = arr1.map(e => arr2[0].value + e.value);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Test that we're not hoisting property reads from lambdas that are created to
* pass to opaque functions, which often have maybe-invoke semantics.
*
* In this example, we shouldn't hoist `arr[0].value` out of the lambda.
* ```js
* e => arr[0].value + e.value <-- created to pass to map
* arr.map(<cb>) <-- argument only invoked if array is non-empty
* ```
*/
function useFoo(t0) {
const $ = _c(12);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr1) {
let t2;
if ($[2] !== arr1[0]) {
t2 = (e) => arr1[0].value + e.value;
$[2] = arr1[0];
$[3] = t2;
} else {
t2 = $[3];
}
t1 = arr1.map(t2);
$[0] = arr1;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
let t2;
if ($[4] !== arr1 || $[5] !== arr2) {
let t3;
if ($[7] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[7] = arr2;
$[8] = t3;
} else {
t3 = $[8];
}
t2 = arr1.map(t3);
$[4] = arr1;
$[5] = arr2;
$[6] = t2;
} else {
t2 = $[6];
}
const y = t2;
let t3;
if ($[9] !== x || $[10] !== y) {
t3 = [x, y];
$[9] = x;
$[10] = y;
$[11] = t3;
} else {
t3 = $[11];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) [[],[]]
[[],[]]
[[2,3],[0,1]]

View File

@@ -0,0 +1,25 @@
/**
* Test that we're not hoisting property reads from lambdas that are created to
* pass to opaque functions, which often have maybe-invoke semantics.
*
* In this example, we shouldn't hoist `arr[0].value` out of the lambda.
* ```js
* e => arr[0].value + e.value <-- created to pass to map
* arr.map(<cb>) <-- argument only invoked if array is non-empty
* ```
*/
function useFoo({arr1, arr2}) {
const x = arr1.map(e => arr1[0].value + e.value);
const y = arr1.map(e => arr2[0].value + e.value);
return [x, y];
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -0,0 +1,112 @@
## Input
```javascript
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
function Component({a, b}) {
const logA = () => {
console.log(a.value);
};
const logB = () => {
console.log(b.value);
};
const hasLogged = useRef(false);
const log = () => {
if (!hasLogged.current) {
logA();
logB();
hasLogged.current = true;
}
};
return <Stringify log={log} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: {value: 1}, b: {value: 2}}],
sequentialRenders: [
{a: {value: 1}, b: {value: 2}},
{a: {value: 3}, b: {value: 4}},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
import { Stringify } from "shared-runtime";
function Component(t0) {
const $ = _c(9);
const { a, b } = t0;
let t1;
if ($[0] !== a.value) {
t1 = () => {
console.log(a.value);
};
$[0] = a.value;
$[1] = t1;
} else {
t1 = $[1];
}
const logA = t1;
let t2;
if ($[2] !== b.value) {
t2 = () => {
console.log(b.value);
};
$[2] = b.value;
$[3] = t2;
} else {
t2 = $[3];
}
const logB = t2;
const hasLogged = useRef(false);
let t3;
if ($[4] !== logA || $[5] !== logB) {
t3 = () => {
if (!hasLogged.current) {
logA();
logB();
hasLogged.current = true;
}
};
$[4] = logA;
$[5] = logB;
$[6] = t3;
} else {
t3 = $[6];
}
const log = t3;
let t4;
if ($[7] !== log) {
t4 = <Stringify log={log} shouldInvokeFns={true} />;
$[7] = log;
$[8] = t4;
} else {
t4 = $[8];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: { value: 1 }, b: { value: 2 } }],
sequentialRenders: [
{ a: { value: 1 }, b: { value: 2 } },
{ a: { value: 3 }, b: { value: 4 } },
],
};
```
### Eval output
(kind: ok) <div>{"log":{"kind":"Function"},"shouldInvokeFns":true}</div>
<div>{"log":{"kind":"Function"},"shouldInvokeFns":true}</div>
logs: [1,2]

View File

@@ -0,0 +1,29 @@
import {useRef} from 'react';
import {Stringify} from 'shared-runtime';
function Component({a, b}) {
const logA = () => {
console.log(a.value);
};
const logB = () => {
console.log(b.value);
};
const hasLogged = useRef(false);
const log = () => {
if (!hasLogged.current) {
logA();
logB();
hasLogged.current = true;
}
};
return <Stringify log={log} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: {value: 1}, b: {value: 2}}],
sequentialRenders: [
{a: {value: 1}, b: {value: 2}},
{a: {value: 3}, b: {value: 4}},
],
};

View File

@@ -0,0 +1,85 @@
## Input
```javascript
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== 0) setState(obj.value);
};
useIdentity(null);
if (state === 0) {
cb();
}
return {cb};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useState } from "react";
import { useIdentity } from "shared-runtime";
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback(t0) {
const $ = _c(4);
const { obj } = t0;
const [state, setState] = useState(0);
let t1;
if ($[0] !== obj.value) {
t1 = () => {
if (obj.value !== 0) {
setState(obj.value);
}
};
$[0] = obj.value;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
useIdentity(null);
if (state === 0) {
cb();
}
let t2;
if ($[2] !== cb) {
t2 = { cb };
$[2] = cb;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 } }],
sequentialRenders: [{ obj: { value: 1 } }, { obj: { value: 2 } }],
};
```
### Eval output
(kind: ok) {"cb":"[[ function params=0 ]]"}
{"cb":"[[ function params=0 ]]"}

View File

@@ -0,0 +1,23 @@
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== 0) setState(obj.value);
};
useIdentity(null);
if (state === 0) {
cb();
}
return {cb};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};

View File

@@ -0,0 +1,87 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that conditionally returned functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldMakeCb,
setState,
}: {
obj: {value: number};
shouldMakeCb: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
if (shouldMakeCb) return cb;
else return null;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, shouldMakeCb: true, setState}],
sequentialRenders: [
{obj: {value: 1}, shouldMakeCb: true, setState},
{obj: {value: 2}, shouldMakeCb: true, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
/**
* Assume that conditionally returned functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, shouldMakeCb, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = t1;
if (shouldMakeCb) {
return cb;
} else {
return null;
}
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, shouldMakeCb: true, setState }],
sequentialRenders: [
{ obj: { value: 1 }, shouldMakeCb: true, setState },
{ obj: { value: 2 }, shouldMakeCb: true, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,32 @@
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that conditionally returned functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldMakeCb,
setState,
}: {
obj: {value: number};
shouldMakeCb: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
if (shouldMakeCb) return cb;
else return null;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, shouldMakeCb: true, setState}],
sequentialRenders: [
{obj: {value: 1}, shouldMakeCb: true, setState},
{obj: {value: 2}, shouldMakeCb: true, setState},
],
};

View File

@@ -0,0 +1,74 @@
## Input
```javascript
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== state) setState(obj.value);
};
useIdentity();
cb();
return [cb];
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useState } from "react";
import { useIdentity } from "shared-runtime";
function useMakeCallback(t0) {
const $ = _c(5);
const { obj } = t0;
const [state, setState] = useState(0);
let t1;
if ($[0] !== obj.value || $[1] !== state) {
t1 = () => {
if (obj.value !== state) {
setState(obj.value);
}
};
$[0] = obj.value;
$[1] = state;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = t1;
useIdentity();
cb();
let t2;
if ($[3] !== cb) {
t2 = [cb];
$[3] = cb;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 } }],
sequentialRenders: [{ obj: { value: 1 } }, { obj: { value: 2 } }],
};
```
### Eval output
(kind: ok) ["[[ function params=0 ]]"]
["[[ function params=0 ]]"]

View File

@@ -0,0 +1,17 @@
import {useState} from 'react';
import {useIdentity} from 'shared-runtime';
function useMakeCallback({obj}: {obj: {value: number}}) {
const [state, setState] = useState(0);
const cb = () => {
if (obj.value !== state) setState(obj.value);
};
useIdentity();
cb();
return [cb];
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};

View File

@@ -0,0 +1,130 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
/**
* (Given that the returned lambda is assumed to be invoked, see
* return-function)
*
* If lambda A conditionally calls lambda B, optimistically assume that property
* loads from lambda B has the same hoistability of ones from lambda A. This
* helps optimize components / hooks that create and chain many helper
* functions.
*
* Type systems and code readability encourage developers to colocate length and
* null checks values in the same function as where values are used. i.e.
* developers are unlikely to write the following code.
* ```js
* function useFoo(obj, objNotNullAndHasElements) {
* // ...
* const get0th = () => obj.arr[0].value;
* return () => objNotNullAndHasElements ? get0th : undefined;
* }
* ```
*
* In Meta code, this assumption helps reduce the number of memo dependency
* deopts.
*/
function useMakeCallback({
obj,
cond,
setState,
}: {
obj: {value: number};
cond: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
// cb's property loads are assumed to be hoistable to the start of this lambda
return () => (cond ? cb() : undefined);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, cond: true, setState}],
sequentialRenders: [
{obj: {value: 1}, cond: true, setState},
{obj: {value: 2}, cond: true, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
/**
* (Given that the returned lambda is assumed to be invoked, see
* return-function)
*
* If lambda A conditionally calls lambda B, optimistically assume that property
* loads from lambda B has the same hoistability of ones from lambda A. This
* helps optimize components / hooks that create and chain many helper
* functions.
*
* Type systems and code readability encourage developers to colocate length and
* null checks values in the same function as where values are used. i.e.
* developers are unlikely to write the following code.
* ```js
* function useFoo(obj, objNotNullAndHasElements) {
* // ...
* const get0th = () => obj.arr[0].value;
* return () => objNotNullAndHasElements ? get0th : undefined;
* }
* ```
*
* In Meta code, this assumption helps reduce the number of memo dependency
* deopts.
*/
function useMakeCallback(t0) {
const $ = _c(6);
const { obj, cond, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = t1;
let t2;
if ($[3] !== cb || $[4] !== cond) {
t2 = () => (cond ? cb() : undefined);
$[3] = cb;
$[4] = cond;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, cond: true, setState }],
sequentialRenders: [
{ obj: { value: 1 }, cond: true, setState },
{ obj: { value: 2 }, cond: true, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,51 @@
import {createHookWrapper} from 'shared-runtime';
/**
* (Given that the returned lambda is assumed to be invoked, see
* return-function)
*
* If lambda A conditionally calls lambda B, optimistically assume that property
* loads from lambda B has the same hoistability of ones from lambda A. This
* helps optimize components / hooks that create and chain many helper
* functions.
*
* Type systems and code readability encourage developers to colocate length and
* null checks values in the same function as where values are used. i.e.
* developers are unlikely to write the following code.
* ```js
* function useFoo(obj, objNotNullAndHasElements) {
* // ...
* const get0th = () => obj.arr[0].value;
* return () => objNotNullAndHasElements ? get0th : undefined;
* }
* ```
*
* In Meta code, this assumption helps reduce the number of memo dependency
* deopts.
*/
function useMakeCallback({
obj,
cond,
setState,
}: {
obj: {value: number};
cond: boolean;
setState: (newState: number) => void;
}) {
const cb = () => setState(obj.value);
// cb's property loads are assumed to be hoistable to the start of this lambda
return () => (cond ? cb() : undefined);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, cond: true, setState}],
sequentialRenders: [
{obj: {value: 1}, cond: true, setState},
{obj: {value: 2}, cond: true, setState},
],
};

View File

@@ -0,0 +1,80 @@
## Input
```javascript
import {createHookWrapper, useIdentity} from 'shared-runtime';
/**
* Assume that functions passed hook arguments are invoked and that their
* property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
const cb = useIdentity(() => setState(obj.value));
return cb;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper, useIdentity } from "shared-runtime";
/**
* Assume that functions passed hook arguments are invoked and that their
* property loads are hoistable.
*/
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
const cb = useIdentity(t1);
return cb;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, setState }],
sequentialRenders: [
{ obj: { value: 1 }, setState },
{ obj: { value: 2 }, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,29 @@
import {createHookWrapper, useIdentity} from 'shared-runtime';
/**
* Assume that functions passed hook arguments are invoked and that their
* property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
const cb = useIdentity(() => setState(obj.value));
return cb;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};

View File

@@ -0,0 +1,80 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
function useFoo({arr1}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
return [x, cb1];
}
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useFoo),
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
function useFoo(t0) {
const $ = _c(8);
const { arr1 } = t0;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
} else {
t1 = $[1];
}
const cb1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
} else {
t2 = $[4];
}
const x = t2;
let t3;
if ($[5] !== cb1 || $[6] !== x) {
t3 = [x, cb1];
$[5] = cb1;
$[6] = x;
$[7] = t3;
} else {
t3 = $[7];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useFoo),
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```
### Eval output
(kind: ok) <div>{"result":[[],"[[ function params=1 ]]"],"shouldInvokeFns":true}</div>
<div>{"result":[[],"[[ function params=1 ]]"],"shouldInvokeFns":true}</div>
<div>{"result":[[2,3],"[[ function params=1 ]]"],"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,17 @@
import {createHookWrapper} from 'shared-runtime';
function useFoo({arr1}) {
const cb1 = e => arr1[0].value + e.value;
const x = arr1.map(cb1);
return [x, cb1];
}
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useFoo),
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -0,0 +1,75 @@
## Input
```javascript
// @flow
import {Stringify} from 'shared-runtime';
/**
* Assume that functions captured directly as jsx attributes are invoked and
* that their property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return <Stringify cb={() => setState(obj.value)} shouldInvokeFns={true} />;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = <Stringify cb={() => setState(obj.value)} shouldInvokeFns={true} />;
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 }, setState }],
sequentialRenders: [
{ obj: { value: 1 }, setState },
{ obj: { value: 2 }, setState },
],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"cb":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,29 @@
// @flow
import {Stringify} from 'shared-runtime';
/**
* Assume that functions captured directly as jsx attributes are invoked and
* that their property loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return <Stringify cb={() => setState(obj.value)} shouldInvokeFns={true} />;
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};

View File

@@ -0,0 +1,78 @@
## Input
```javascript
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that directly returned functions are invoked and that their property
* loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return () => setState(obj.value);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
/**
* Assume that directly returned functions are invoked and that their property
* loads are hoistable.
*/
function useMakeCallback(t0) {
const $ = _c(3);
const { obj, setState } = t0;
let t1;
if ($[0] !== obj.value || $[1] !== setState) {
t1 = () => setState(obj.value);
$[0] = obj.value;
$[1] = setState;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
const setState = (arg: number) => {
"use no memo";
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{ obj: { value: 1 }, setState }],
sequentialRenders: [
{ obj: { value: 1 }, setState },
{ obj: { value: 2 }, setState },
],
};
```
### Eval output
(kind: ok) <div>{"result":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"result":{"kind":"Function","result":2},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,28 @@
import {createHookWrapper} from 'shared-runtime';
/**
* Assume that directly returned functions are invoked and that their property
* loads are hoistable.
*/
function useMakeCallback({
obj,
setState,
}: {
obj: {value: number};
setState: (newState: number) => void;
}) {
return () => setState(obj.value);
}
const setState = (arg: number) => {
'use no memo';
return arg;
};
export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useMakeCallback),
params: [{obj: {value: 1}, setState}],
sequentialRenders: [
{obj: {value: 1}, setState},
{obj: {value: 2}, setState},
],
};

View File

@@ -0,0 +1,82 @@
## Input
```javascript
import {useState, useMemo} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldSynchronizeState,
}: {
obj: {value: number};
shouldSynchronizeState: boolean;
}) {
const [state, setState] = useState(0);
const cb = useMemo(() => {
return () => {
if (obj.value !== 0) setState(obj.value);
};
}, [obj.value, shouldSynchronizeState]);
useIdentity(null);
return cb;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useState, useMemo } from "react";
import { useIdentity } from "shared-runtime";
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback(t0) {
const $ = _c(2);
const { obj, shouldSynchronizeState } = t0;
const [, setState] = useState(0);
let t1;
let t2;
if ($[0] !== obj.value) {
t2 = () => {
if (obj.value !== 0) {
setState(obj.value);
}
};
$[0] = obj.value;
$[1] = t2;
} else {
t2 = $[1];
}
t1 = t2;
const cb = t1;
useIdentity(null);
return cb;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ obj: { value: 1 } }],
sequentialRenders: [{ obj: { value: 1 } }, { obj: { value: 2 } }],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"
"[[ function params=0 ]]"

View File

@@ -0,0 +1,28 @@
import {useState, useMemo} from 'react';
import {useIdentity} from 'shared-runtime';
/**
* Assume that conditionally called functions can be invoked and that their
* property loads are hoistable to the function declaration site.
*/
function useMakeCallback({
obj,
shouldSynchronizeState,
}: {
obj: {value: number};
shouldSynchronizeState: boolean;
}) {
const [state, setState] = useState(0);
const cb = useMemo(() => {
return () => {
if (obj.value !== 0) setState(obj.value);
};
}, [obj.value, shouldSynchronizeState]);
useIdentity(null);
return cb;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{obj: {value: 1}}],
sequentialRenders: [{obj: {value: 1}}, {obj: {value: 2}}],
};

View File

@@ -0,0 +1,68 @@
## Input
```javascript
function useFoo({arr1, arr2}) {
const cb = e => arr2[0].value + e.value;
const y = [];
for (let i = 0; i < arr1.length; i++) {
y.push(cb(arr1[i]));
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useFoo(t0) {
const $ = _c(5);
const { arr1, arr2 } = t0;
let t1;
if ($[0] !== arr2[0].value) {
t1 = (e) => arr2[0].value + e.value;
$[0] = arr2[0].value;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
let y;
if ($[2] !== arr1 || $[3] !== cb) {
y = [];
for (let i = 0; i < arr1.length; i++) {
y.push(cb(arr1[i]));
}
$[2] = arr1;
$[3] = cb;
$[4] = y;
} else {
y = $[4];
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{ arr1: [], arr2: [] }],
sequentialRenders: [
{ arr1: [], arr2: [] },
{ arr1: [], arr2: null },
{ arr1: [{ value: 1 }, { value: 2 }], arr2: [{ value: -1 }] },
],
};
```

View File

@@ -0,0 +1,18 @@
function useFoo({arr1, arr2}) {
const cb = e => arr2[0].value + e.value;
const y = [];
for (let i = 0; i < arr1.length; i++) {
y.push(cb(arr1[i]));
}
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [{arr1: [], arr2: []}],
sequentialRenders: [
{arr1: [], arr2: []},
{arr1: [], arr2: null},
{arr1: [{value: 1}, {value: 2}], arr2: [{value: -1}]},
],
};

View File

@@ -0,0 +1,57 @@
## Input
```javascript
/**
* Assume that only directly returned functions or JSX attributes are invoked.
* Conservatively estimate that functions wrapped in objects or other containers
* might never be called (and therefore their property loads are not hoistable).
*/
function useMakeCallback({arr}) {
return {
getElement0: () => arr[0].value,
getElement1: () => arr[1].value,
};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{arr: [1, 2]}],
sequentialRenders: [{arr: [1, 2]}, {arr: []}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; /**
* Assume that only directly returned functions or JSX attributes are invoked.
* Conservatively estimate that functions wrapped in objects or other containers
* might never be called (and therefore their property loads are not hoistable).
*/
function useMakeCallback(t0) {
const $ = _c(2);
const { arr } = t0;
let t1;
if ($[0] !== arr) {
t1 = { getElement0: () => arr[0].value, getElement1: () => arr[1].value };
$[0] = arr;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{ arr: [1, 2] }],
sequentialRenders: [{ arr: [1, 2] }, { arr: [] }],
};
```
### Eval output
(kind: ok) {"getElement0":"[[ function params=0 ]]","getElement1":"[[ function params=0 ]]"}
{"getElement0":"[[ function params=0 ]]","getElement1":"[[ function params=0 ]]"}

View File

@@ -0,0 +1,17 @@
/**
* Assume that only directly returned functions or JSX attributes are invoked.
* Conservatively estimate that functions wrapped in objects or other containers
* might never be called (and therefore their property loads are not hoistable).
*/
function useMakeCallback({arr}) {
return {
getElement0: () => arr[0].value,
getElement1: () => arr[1].value,
};
}
export const FIXTURE_ENTRYPOINT = {
fn: useMakeCallback,
params: [{arr: [1, 2]}],
sequentialRenders: [{arr: [1, 2]}, {arr: []}],
};

View File

@@ -41,9 +41,9 @@ function useFoo(t0) {
local = $[1];
}
let t1;
if ($[2] !== local.b.c) {
if ($[2] !== local) {
t1 = () => [() => local.b.c];
$[2] = local.b.c;
$[2] = local;
$[3] = t1;
} else {
t1 = $[3];

View File

@@ -34,13 +34,13 @@ function useFoo(t0) {
const $ = _c(4);
const { a } = t0;
let t1;
if ($[0] !== a.b.c) {
if ($[0] !== a) {
t1 = {
fn() {
return identity(a.b.c);
},
};
$[0] = a.b.c;
$[0] = a;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -51,7 +51,7 @@ import { identity } from "shared-runtime";
function Component(props) {
const $ = _c(4);
let x;
if ($[0] !== props.cond) {
if ($[0] !== props) {
const f = () => {
if (props.cond) {
x = 1;
@@ -62,7 +62,7 @@ function Component(props) {
const f2 = identity(f);
f2();
$[0] = props.cond;
$[0] = props;
$[1] = x;
} else {
x = $[1];

View File

@@ -82,9 +82,9 @@ function Component(t0) {
contextVar = $[2];
}
let t1;
if ($[3] !== contextVar.val) {
if ($[3] !== contextVar) {
t1 = { cb: () => contextVar.val * 4 };
$[3] = contextVar.val;
$[3] = contextVar;
$[4] = t1;
} else {
t1 = $[4];

View File

@@ -43,7 +43,7 @@ const t0 = "module_t0";
const c_0 = "module_c_0";
function useFoo(props) {
const $0 = _c(2);
const c_00 = $0[0] !== props.value;
const c_00 = $0[0] !== props;
let t1;
if (c_00) {
const a = {
@@ -61,7 +61,7 @@ function useFoo(props) {
};
t1 = a.foo().bar();
$0[0] = props.value;
$0[0] = props;
$0[1] = t1;
} else {
t1 = $0[1];

View File

@@ -35,7 +35,7 @@ function Component(props) {
import { c as _c } from "react/compiler-runtime"; // @enableTransitivelyFreezeFunctionExpressions:false
function Component(props) {
const $ = _c(9);
const $ = _c(7);
const item = useMutable(props.itemId);
const dispatch = useDispatch();
useFreeze(dispatch);
@@ -51,7 +51,8 @@ function Component(props) {
}
const exit = t0;
let t1;
if ($[2] !== exit || $[3] !== item.value) {
let t2;
if ($[2] !== exit || $[3] !== item) {
t1 = () => {
const cleanup = GlobalEventEmitter.addListener("onInput", () => {
if (item.value) {
@@ -60,30 +61,24 @@ function Component(props) {
});
return () => cleanup.remove();
};
t2 = [exit, item];
$[2] = exit;
$[3] = item.value;
$[3] = item;
$[4] = t1;
$[5] = t2;
} else {
t1 = $[4];
}
let t2;
if ($[5] !== exit || $[6] !== item) {
t2 = [exit, item];
$[5] = exit;
$[6] = item;
$[7] = t2;
} else {
t2 = $[7];
t2 = $[5];
}
useEffect(t1, t2);
maybeMutate(item);
let t3;
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t3 = <div />;
$[8] = t3;
$[6] = t3;
} else {
t3 = $[8];
t3 = $[6];
}
return t3;
}

View File

@@ -9,6 +9,8 @@ import {ErrorSeverity} from 'babel-plugin-react-compiler/src';
import {RuleTester as ESLintTester} from 'eslint';
import ReactCompilerRule from '../src/rules/ReactCompilerRule';
const ESLintTesterV8 = require('eslint-v8').RuleTester;
/**
* A string template tag that removes padding from the left side of multi-line strings
* @param {Array} strings array of code strings (only one expected)
@@ -309,7 +311,7 @@ const tests: CompilerTestCases = {
],
};
const eslintTester = new ESLintTester({
const eslintTester = new ESLintTesterV8({
parser: require.resolve('hermes-eslint'),
parserOptions: {
ecmaVersion: 2015,

View File

@@ -8,6 +8,8 @@
import {RuleTester} from 'eslint';
import ReactCompilerRule from '../src/rules/ReactCompilerRule';
const ESLintTesterV8 = require('eslint-v8').RuleTester;
/**
* A string template tag that removes padding from the left side of multi-line strings
* @param {Array} strings array of code strings (only one expected)
@@ -70,7 +72,7 @@ const tests: CompilerTestCases = {
],
};
const eslintTester = new RuleTester({
const eslintTester = new ESLintTesterV8({
parser: require.resolve('@typescript-eslint/parser'),
});
eslintTester.run('react-compiler', ReactCompilerRule, tests);

View File

@@ -26,7 +26,7 @@
"@types/eslint": "^8.56.12",
"@types/node": "^20.2.5",
"babel-jest": "^29.0.3",
"eslint": "8.57.0",
"eslint-v8": "npm:eslint@^8.57.1",
"hermes-eslint": "^0.25.1",
"jest": "^29.5.0"
},

View File

@@ -25,7 +25,7 @@ const configs = {
},
},
rules: {
'react-compiler/react-compiler': 'error',
'react-compiler/react-compiler': 'error' as const,
},
},
};

View File

@@ -7675,61 +7675,60 @@ const tests = {
],
};
if (__EXPERIMENTAL__) {
tests.valid = [
...tests.valid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, []);
}
`,
},
];
tests.valid = [
...tests.valid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, []);
}
`,
},
];
tests.invalid = [
...tests.invalid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, [onStuff]);
}
`,
errors: [
{
message:
'Functions returned from `useEffectEvent` must not be included in the dependency array. ' +
'Remove `onStuff` from the list.',
suggestions: [
{
desc: 'Remove the dependency `onStuff`',
output: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, []);
}
`,
},
],
},
],
},
];
}
// useEffectEvent
tests.invalid = [
...tests.invalid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, [onStuff]);
}
`,
errors: [
{
message:
'Functions returned from `useEffectEvent` must not be included in the dependency array. ' +
'Remove `onStuff` from the list.',
suggestions: [
{
desc: 'Remove the dependency `onStuff`',
output: normalizeIndent`
function MyComponent({ theme }) {
const onStuff = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onStuff();
}, []);
}
`,
},
],
},
],
},
];
// Tests that are only valid/invalid across parsers supporting Flow
const testsFlow = {
@@ -8370,16 +8369,16 @@ describe('rules-of-hooks/exhaustive-deps', () => {
testsTypescriptEslintParser
);
new ESLintTesterV9({
languageOptions: {
...languageOptionsV9,
parser: require('@typescript-eslint/parser-v3'),
},
}).run(
'eslint: v9, parser: @typescript-eslint/parser@3.x',
ReactHooksESLintRule,
testsTypescriptEslintParser
);
// new ESLintTesterV9({
// languageOptions: {
// ...languageOptionsV9,
// parser: require('@typescript-eslint/parser-v3'),
// },
// }).run(
// 'eslint: v9, parser: @typescript-eslint/parser@3.x',
// ReactHooksESLintRule,
// testsTypescriptEslintParser
// );
new ESLintTesterV7({
parser: require.resolve('@typescript-eslint/parser-v4'),

View File

@@ -1286,180 +1286,181 @@ const tests = {
],
};
if (__EXPERIMENTAL__) {
tests.valid = [
...tests.valid,
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be called in a useEffect.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onClick();
});
}
`,
},
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
}
`,
},
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
const onClick2 = () => { onClick() };
const onClick3 = useCallback(() => onClick(), []);
return <>
<Child onClick={onClick2}></Child>
<Child onClick={onClick3}></Child>
</>;
}
`,
},
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be passed by reference in useEffect
// and useEffectEvent.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
const onClick2 = useEffectEvent(() => {
debounce(onClick);
});
useEffect(() => {
let id = setInterval(onClick, 100);
return () => clearInterval(onClick);
}, []);
return <Child onClick={() => onClick2()} />
}
`,
},
{
code: normalizeIndent`
const MyComponent = ({theme}) => {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
};
`,
},
{
code: normalizeIndent`
function MyComponent({ theme }) {
const notificationService = useNotifications();
const showNotification = useEffectEvent((text) => {
notificationService.notify(theme, text);
});
const onClick = useEffectEvent((text) => {
showNotification(text);
});
return <Child onClick={(text) => onClick(text)} />
}
`,
},
{
code: normalizeIndent`
function MyComponent({ theme }) {
useEffect(() => {
onClick();
});
const onClick = useEffectEvent(() => {
showNotification(theme);
});
}
`,
},
];
tests.invalid = [
...tests.invalid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
errors: [useEffectEventError('onClick')],
},
{
code: normalizeIndent`
// This should error even though it shares an identifier name with the below
function MyComponent({theme}) {
const onClick = useEffectEvent(() => {
showNotification(theme)
});
return <Child onClick={onClick} />
}
// useEffectEvent
tests.valid = [
...tests.valid,
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be called in a useEffect.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
useEffect(() => {
onClick();
});
}
`,
},
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
}
`,
},
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be called in closures.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
const onClick2 = () => { onClick() };
const onClick3 = useCallback(() => onClick(), []);
return <>
<Child onClick={onClick2}></Child>
<Child onClick={onClick3}></Child>
</>;
}
`,
},
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be passed by reference in useEffect
// and useEffectEvent.
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
const onClick2 = useEffectEvent(() => {
debounce(onClick);
});
useEffect(() => {
let id = setInterval(onClick, 100);
return () => clearInterval(onClick);
}, []);
return <Child onClick={() => onClick2()} />
}
`,
},
{
code: normalizeIndent`
const MyComponent = ({theme}) => {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={() => onClick()}></Child>;
};
`,
},
{
code: normalizeIndent`
function MyComponent({ theme }) {
const notificationService = useNotifications();
const showNotification = useEffectEvent((text) => {
notificationService.notify(theme, text);
});
const onClick = useEffectEvent((text) => {
showNotification(text);
});
return <Child onClick={(text) => onClick(text)} />
}
`,
},
{
code: normalizeIndent`
function MyComponent({ theme }) {
useEffect(() => {
onClick();
});
const onClick = useEffectEvent(() => {
showNotification(theme);
});
}
`,
},
];
// The useEffectEvent function shares an identifier name with the above
function MyOtherComponent({theme}) {
const onClick = useEffectEvent(() => {
showNotification(theme)
});
return <Child onClick={() => onClick()} />
}
`,
errors: [{...useEffectEventError('onClick'), line: 7}],
},
{
code: normalizeIndent`
const MyComponent = ({ theme }) => {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
errors: [useEffectEventError('onClick')],
},
{
code: normalizeIndent`
// Invalid because onClick is being aliased to foo but not invoked
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
let foo = onClick;
return <Bar onClick={foo} />
}
`,
errors: [{...useEffectEventError('onClick'), line: 7}],
},
{
code: normalizeIndent`
// Should error because it's being passed down to JSX, although it's been referenced once
// in an effect
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(them);
});
useEffect(() => {
setTimeout(onClick, 100);
});
return <Child onClick={onClick} />
}
`,
errors: [useEffectEventError('onClick')],
},
];
}
// useEffectEvent
tests.invalid = [
...tests.invalid,
{
code: normalizeIndent`
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
errors: [useEffectEventError('onClick')],
},
{
code: normalizeIndent`
// This should error even though it shares an identifier name with the below
function MyComponent({theme}) {
const onClick = useEffectEvent(() => {
showNotification(theme)
});
return <Child onClick={onClick} />
}
// The useEffectEvent function shares an identifier name with the above
function MyOtherComponent({theme}) {
const onClick = useEffectEvent(() => {
showNotification(theme)
});
return <Child onClick={() => onClick()} />
}
`,
errors: [{...useEffectEventError('onClick'), line: 7}],
},
{
code: normalizeIndent`
const MyComponent = ({ theme }) => {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
return <Child onClick={onClick}></Child>;
}
`,
errors: [useEffectEventError('onClick')],
},
{
code: normalizeIndent`
// Invalid because onClick is being aliased to foo but not invoked
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(theme);
});
let foo = onClick;
return <Bar onClick={foo} />
}
`,
errors: [{...useEffectEventError('onClick'), line: 7}],
},
{
code: normalizeIndent`
// Should error because it's being passed down to JSX, although it's been referenced once
// in an effect
function MyComponent({ theme }) {
const onClick = useEffectEvent(() => {
showNotification(them);
});
useEffect(() => {
setTimeout(onClick, 100);
});
return <Child onClick={onClick} />
}
`,
errors: [useEffectEventError('onClick')],
},
];
function conditionalError(hook, hasPreviousFinalizer = false) {
return {
@@ -1623,16 +1624,16 @@ describe('rules-of-hooks/rules-of-hooks', () => {
tests
);
new ESLintTesterV9({
languageOptions: {
...languageOptionsV9,
parser: require('@typescript-eslint/parser-v3'),
},
}).run(
'eslint: v9, parser: @typescript-eslint/parser@3.x',
ReactHooksESLintRule,
tests
);
// new ESLintTesterV9({
// languageOptions: {
// ...languageOptionsV9,
// parser: require('@typescript-eslint/parser-v3'),
// },
// }).run(
// 'eslint: v9, parser: @typescript-eslint/parser@3.x',
// ReactHooksESLintRule,
// tests
// );
new ESLintTesterV7({
parser: require.resolve('@typescript-eslint/parser-v4'),

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
presets: [
['@babel/preset-env', {targets: {esmodules: false, node: 'current'}}],
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-flow-strip-types',
['@babel/plugin-proposal-class-properties', {loose: true}],
'@babel/plugin-transform-classes',
['@babel/plugin-transform-private-property-in-object', {loose: true}],
['@babel/plugin-transform-private-methods', {loose: true}],
],
};

View File

@@ -0,0 +1,11 @@
/** @type {import('jest').Config} */
const config = {
transform: {
'\\.[jt]sx?$': [
'babel-jest',
{configFile: require.resolve('./babel.config.js')},
],
},
};
module.exports = config;

View File

@@ -5,7 +5,7 @@
"repository": {
"type": "git",
"url": "https://github.com/facebook/react.git",
"directory": "packages/eslint-plugin-react-hooks"
"directory": "compiler/packages/eslint-plugin-react-hooks"
},
"files": [
"LICENSE",
@@ -21,9 +21,11 @@
"react"
],
"scripts": {
"build:compiler": "cd ../../compiler && yarn workspace babel-plugin-react-compiler build",
"build:compiler": "yarn workspace babel-plugin-react-compiler build",
"build": "rimraf dist && tsup",
"test": "yarn build:compiler && jest",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"watch": "yarn build --watch"
},
"license": "MIT",
"bugs": {
@@ -48,6 +50,7 @@
},
"devDependencies": {
"@babel/eslint-parser": "^7.11.4",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-typescript": "^7.26.0",
"@babel/types": "^7.19.0",
"@tsconfig/strictest": "^2.0.5",
@@ -60,6 +63,7 @@
"@types/estree-jsx": "^1.0.5",
"@types/node": "^20.2.5",
"babel-eslint": "^10.0.3",
"babel-jest": "^29.7.0",
"eslint-v7": "npm:eslint@^7.7.0",
"eslint-v8": "npm:eslint@^8.57.1",
"eslint-v9": "npm:eslint@^9.0.0",

View File

@@ -2056,10 +2056,7 @@ function isAncestorNodeOf(a: Node, b: Node): boolean {
}
function isUseEffectEventIdentifier(node: Node): boolean {
if (__EXPERIMENTAL__) {
return node.type === 'Identifier' && node.name === 'useEffectEvent';
}
return false;
return node.type === 'Identifier' && node.name === 'useEffectEvent';
}
function getUnknownDependenciesMessage(reactiveHookName: string): string {

View File

@@ -109,10 +109,7 @@ function isInsideDoWhileLoop(node: Node | undefined): node is DoWhileStatement {
}
function isUseEffectEventIdentifier(node: Node): boolean {
if (__EXPERIMENTAL__) {
return node.type === 'Identifier' && node.name === 'useEffectEvent';
}
return false;
return node.type === 'Identifier' && node.name === 'useEffectEvent';
}
function isUseIdentifier(node: Node): boolean {

View File

@@ -9,7 +9,7 @@
"types": ["estree-jsx", "node"],
"downlevelIteration": true,
"paths": {
"babel-plugin-react-compiler": ["../../compiler/packages/babel-plugin-react-compiler/src"]
"babel-plugin-react-compiler": ["../babel-plugin-react-compiler/src"]
},
"jsx": "react-jsxdev",
"rootDir": "../..",

View File

@@ -0,0 +1,36 @@
import {defineConfig} from 'tsup';
export default defineConfig({
entry: ['./src/index.ts'],
outDir: './dist',
external: [
'@babel/core',
'@babel/parser',
'@babel/plugin-proposal-private-methods',
'hermes-parser',
'zod',
'zod-validation-error',
],
splitting: false,
sourcemap: false,
dts: false,
bundle: true,
format: 'cjs',
platform: 'node',
banner: {
js: `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @lightSyntaxTransform
* @noflow
* @nolint
* @preventMunge
* @preserve-invariant-messages
*/
"use no memo";`,
},
});

View File

@@ -450,6 +450,7 @@ const skipFilter = new Set([
'invalid-jsx-lowercase-localvar',
// bugs
'inner-function/nullable-objects/bug-invalid-array-map-manual',
'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr',
`bug-capturing-func-maybealias-captured-mutate`,
'bug-aliased-capture-aliased-mutate',

View File

@@ -1,6 +1,7 @@
const PUBLISHABLE_PACKAGES = [
'babel-plugin-react-compiler',
'eslint-plugin-react-compiler',
'eslint-plugin-react-hooks',
'react-compiler-healthcheck',
'react-compiler-runtime',
];

File diff suppressed because it is too large Load Diff

View File

@@ -7,3 +7,10 @@ body {
padding: 0;
font-family: sans-serif;
}
::view-transition-group(*),
::view-transition-image-pair(*),
::view-transition-old(*),
::view-transition-new(*) {
pointer-events: none;
}

View File

@@ -92,7 +92,6 @@ export default function SwipeRecognizer({
width: axis === 'x' ? '100%' : null,
height: axis === 'y' ? '100%' : null,
overflow: 'scroll hidden',
touchAction: 'pan-' + direction,
// Disable overscroll on Safari which moves the sticky content.
// Unfortunately, this also means that we disable chaining. We should only disable
// it if the parent is not scrollable in this axis.

View File

@@ -1,8 +0,0 @@
/**
* This file is purely being used for local jest runs, and doesn't participate in the build process.
*/
'use strict';
module.exports = {
extends: '../../babel.config-ts.js',
};

View File

@@ -1,8 +0,0 @@
'use strict';
process.env.NODE_ENV = 'development';
module.exports = {
setupFiles: [require.resolve('../../scripts/jest/setupEnvironment.js')],
moduleFileExtensions: ['ts', 'js', 'json'],
};

View File

@@ -261,7 +261,7 @@ function scheduleFibersWithFamiliesRecursively(
staleFamilies: Set<Family>,
): void {
if (__DEV__) {
const {alternate, child, sibling, tag, type, elementType} = fiber;
const {alternate, child, sibling, tag, type} = fiber;
let candidateType = null;
switch (tag) {
@@ -283,11 +283,6 @@ function scheduleFibersWithFamiliesRecursively(
let needsRender = false;
let needsRemount = false;
if (staleFamilies.has(resolveFamily(elementType))) {
console.log('gonna mark stale fiber for remount');
needsRemount = true;
}
if (candidateType !== null) {
const family = resolveFamily(candidateType);
if (family !== undefined) {

View File

@@ -146,12 +146,6 @@ function canPreserveStateBetween(prevType: any, nextType: any) {
if (isReactClass(prevType) || isReactClass(nextType)) {
return false;
}
if (
typeof prevType !== typeof nextType ||
getProperty(prevType, '$$typeof') !== getProperty(nextType, '$$typeof')
) {
return false;
}
if (haveEqualSignatures(prevType, nextType)) {
return true;
}
@@ -221,7 +215,6 @@ export function performReactRefresh(): RefreshUpdate | null {
if (canPreserveStateBetween(prevType, nextType)) {
updatedFamilies.add(family);
} else {
console.log('mark family as stale', family);
staleFamilies.add(family);
}
});

View File

@@ -699,244 +699,6 @@ describe('ReactFresh', () => {
}
});
fit('can remount when change function to memo', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test() {
return <p>hi test</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check the initial render
const el = container.firstChild;
expect(el.textContent).toBe('hi test');
// Patch to change function to memo
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi memo</p>;
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check remount
expect(container.firstChild !== el).toBe(true);
const nextEl = container.firstChild;
expect(nextEl.textContent).toBe('hi memo');
console.log('patch to original');
// Patch back to original function
await act(async () => {
await patch(() => {
function Test() {
return <p>hi test</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check final remount
expect(container.firstChild !== nextEl).toBe(true);
const newEl = container.firstChild;
expect(newEl.textContent).toBe('hi test');
}
});
it('can remount when change memo to forwardRef', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test2() {
return <p>hi memo</p>;
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check the initial render
const el = container.firstChild;
expect(el.textContent).toBe('hi memo');
// Patch to change memo to forwardRef
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi forwardRef</p>;
}
const Test = React.forwardRef(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check remount
expect(container.firstChild).not.toBe(el);
const nextEl = container.firstChild;
expect(nextEl.textContent).toBe('hi forwardRef');
// Patch back to memo
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi memo</p>;
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check final remount
expect(container.firstChild).not.toBe(nextEl);
const newEl = container.firstChild;
expect(newEl.textContent).toBe('hi memo');
}
});
it('can remount when change function to forwardRef', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test() {
return <p>hi test</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check the initial render
const el = container.firstChild;
expect(el.textContent).toBe('hi test');
// Patch to change function to forwardRef
await act(async () => {
await patch(() => {
function Test2() {
return <p>hi forwardRef</p>;
}
const Test = React.forwardRef(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check remount
expect(container.firstChild).not.toBe(el);
const nextEl = container.firstChild;
expect(nextEl.textContent).toBe('hi forwardRef');
// Patch back to a new function
await act(async () => {
await patch(() => {
function Test() {
return <p>hi test1</p>;
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
// Check final remount
expect(container.firstChild).not.toBe(nextEl);
const newEl = container.firstChild;
expect(newEl.textContent).toBe('hi test1');
}
});
it('resets state when switching between different component types', async () => {
if (__DEV__) {
await act(async () => {
await render(() => {
function Test() {
const [count, setCount] = React.useState(0);
return (
<div onClick={() => setCount(c => c + 1)}>count: {count}</div>
);
}
$RefreshReg$(Test, 'Test');
return Test;
});
});
expect(container.firstChild.textContent).toBe('count: 0');
await act(async () => {
container.firstChild.click();
});
expect(container.firstChild.textContent).toBe('count: 1');
await act(async () => {
await patch(() => {
function Test2() {
const [count, setCount] = React.useState(0);
return (
<div onClick={() => setCount(c => c + 1)}>count: {count}</div>
);
}
const Test = React.memo(Test2);
$RefreshReg$(Test2, 'Test2');
$RefreshReg$(Test, 'Test');
return Test;
});
});
expect(container.firstChild.textContent).toBe('count: 0');
await act(async () => {
container.firstChild.click();
});
expect(container.firstChild.textContent).toBe('count: 1');
await act(async () => {
await patch(() => {
const Test = React.forwardRef((props, ref) => {
const [count, setCount] = React.useState(0);
const handleClick = () => setCount(c => c + 1);
// Ensure ref is extensible
const divRef = React.useRef(null);
React.useEffect(() => {
if (ref) {
if (typeof ref === 'function') {
ref(divRef.current);
} else if (Object.isExtensible(ref)) {
ref.current = divRef.current;
}
}
}, [ref]);
return (
<div ref={divRef} onClick={handleClick}>
count: {count}
</div>
);
});
$RefreshReg$(Test, 'Test');
return Test;
});
});
expect(container.firstChild.textContent).toBe('count: 0');
await act(async () => {
container.firstChild.click();
});
expect(container.firstChild.textContent).toBe('count: 1');
}
});
it('can update simple memo function in isolation', async () => {
if (__DEV__) {
await render(() => {

View File

@@ -7,12 +7,6 @@ module.exports = {
'<rootDir>/scripts/bench/',
],
transform: {
'^.+babel-plugin-react-compiler/dist/index.js$': [
'babel-jest',
{
configFile: require.resolve('../../babel.config-react-compiler.js'),
},
],
'^.+\\.ts$': [
'babel-jest',
{configFile: require.resolve('../../babel.config-ts.js')},

View File

@@ -26,7 +26,6 @@ const {asyncRimRaf} = require('./utils');
const codeFrame = require('@babel/code-frame').default;
const Wrappers = require('./wrappers');
const commonjs = require('@rollup/plugin-commonjs');
const {getBabelOutputPlugin} = require('@rollup/plugin-babel');
const RELEASE_CHANNEL = process.env.RELEASE_CHANNEL;
@@ -418,12 +417,6 @@ function getPlugins(
bundle
)
),
// For Meta internal requirements this package needs to be built targeting ES5.
bundle.name === 'eslint-plugin-react-hooks'
? getBabelOutputPlugin({
presets: ['@babel/preset-env'],
})
: false,
// Remove 'use strict' from individual source files. We skip eslint-plugin-react-hooks because
// it bundles compiler-type code that may examine "use strict" used outside of a directive
// context, e.g. as a StringLiteral.
@@ -503,7 +496,7 @@ function getPlugins(
// takes care of it.
renaming: false,
}),
(needsMinifiedByClosure || bundle.name === 'eslint-plugin-react-hooks') &&
needsMinifiedByClosure &&
// Add the whitespace back
prettier({
parser: 'flow',

View File

@@ -1182,24 +1182,6 @@ const bundles = [
externals: ['react', 'scheduler', 'scheduler/unstable_mock'],
},
/******* ESLint Plugin for Hooks *******/
{
// TODO: we're building this from typescript source now, but there's really
// no reason to have both dev and prod for this package. It's
// currently required in order for the package to be copied over correctly.
// So, it would be worth improving that flow.
name: 'eslint-plugin-react-hooks',
bundleTypes: [NODE_DEV, NODE_PROD, CJS_DTS],
moduleType: ISOMORPHIC,
entry: 'eslint-plugin-react-hooks/src/index.ts',
global: 'ESLintPluginReactHooks',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: [],
tsconfig: './packages/eslint-plugin-react-hooks/tsconfig.json',
prebuild: `mkdir -p ./compiler/packages/babel-plugin-react-compiler/dist && echo "module.exports = require('../src/index.ts');" > ./compiler/packages/babel-plugin-react-compiler/dist/index.js`,
},
/******* React Fresh *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],