Compare commits

..

1 Commits

Author SHA1 Message Date
Mofei Zhang
78856eff76 [compiler][bugfix] Avoid inserting duplicate context variable declarations
(note: also see alternative implementation in https://github.com/facebook/react/pull/32747)

`PruneHoistedContexts` currently strips hoisted declarations and rewrites the first `StoreContext` reassignment to a declaration. For example, in the following example, instruction 0 is removed while a synthetic `DeclareContext let` is inserted before instruction 1.

```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x = 4;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] StoreContext reassign 'x' = 4
[2] StoreContext reassign 'x' = 5
```

Currently, we don't account for `DeclareContext let`. As a result, we're rewriting to insert duplicate declarations.
For the below example, we should only remove instruction 0 (no need to insert a DeclareContext `let` since one is already present).
```js
// source
const cb = () => x; // reference that causes x to be hoisted

let x;
x = 5;

// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] DeclareContext Let 'x'
[2] StoreContext reassign 'x' = 5
```
2025-03-25 14:34:09 -04:00
319 changed files with 4494 additions and 15630 deletions

View File

@@ -1,11 +1,10 @@
{
"packages": ["packages/react", "packages/react-dom", "packages/react-server-dom-webpack", "packages/scheduler"],
"packages": ["packages/react", "packages/react-dom", "packages/scheduler"],
"buildCommand": "download-build-in-codesandbox-ci",
"node": "18",
"publishDirectory": {
"react": "build/oss-experimental/react",
"react-dom": "build/oss-experimental/react-dom",
"react-server-dom-webpack": "build/oss-experimental/react-server-dom-webpack",
"scheduler": "build/oss-experimental/scheduler"
},
"sandboxes": ["new"],

View File

@@ -615,8 +615,6 @@ module.exports = {
GetAnimationsOptions: 'readonly',
Animatable: 'readonly',
ScrollTimeline: 'readonly',
EventListenerOptionsOrUseCapture: 'readonly',
FocusOptions: 'readonly',
spyOnDev: 'readonly',
spyOnDevAndProd: 'readonly',

18
.github/ISSUE_TEMPLATE/19.md vendored Normal file
View File

@@ -0,0 +1,18 @@
---
name: "⚛React 19 beta issue"
about: Report a issue with React 19 beta.
title: '[React 19]'
labels: 'React 19'
---
## Summary
<!--
Please provide a CodeSandbox (https://codesandbox.io/s/new), a link to a
repository on GitHub, or provide a minimal code example that reproduces the
problem. You may provide a screenshot of the application if you think it is
relevant to your bug report. Here are some tips for providing a minimal
example: https://stackoverflow.com/help/mcve.
-->

View File

@@ -1,7 +1,7 @@
name: (Compiler) Discord Notify
on:
pull_request_target:
pull_request:
types: [opened, ready_for_review]
paths:
- compiler/**
@@ -10,19 +10,7 @@ on:
permissions: {}
jobs:
check_access:
runs-on: ubuntu-latest
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
check_maintainer:
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
needs: [check_access]
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer

View File

@@ -40,12 +40,8 @@ jobs:
with:
path: |
**/node_modules
key: compiler-and-playground-node_modules-v6-${{ 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
if: steps.node_modules.outputs.cache-hit != 'true'
working-directory: compiler
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"

View File

@@ -16,9 +16,6 @@ on:
version_name:
required: true
type: string
tag_version:
required: false
type: string
secrets:
NPM_TOKEN:
required: true
@@ -52,10 +49,9 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ 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
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Publish packages to npm
run: |
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag=${{ inputs.dist_tag }} ${{ inputs.tag_version && format('--tagVersion={0}', inputs.tag_version) || '' }}
scripts/release/publish.js --frfr --ci --versionName=${{ inputs.version_name }} --tag ${{ inputs.dist_tag }}

View File

@@ -14,9 +14,6 @@ on:
version_name:
required: true
type: string
tag_version:
required: false
type: string
permissions: {}
@@ -32,6 +29,5 @@ jobs:
release_channel: ${{ inputs.release_channel }}
dist_tag: ${{ inputs.dist_tag }}
version_name: ${{ inputs.version_name }}
tag_version: ${{ inputs.tag_version }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -0,0 +1,23 @@
name: (Compiler) Publish Prereleases Weekly
on:
schedule:
# At 10 minutes past 9:00 on Mon
- cron: 10 9 * * 1
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
publish_prerelease_beta:
name: Publish to beta channel
uses: facebook/react/.github/workflows/compiler_prereleases.yml@main
with:
commit_sha: ${{ github.sha }}
release_channel: beta
dist_tag: beta
version_name: '19.0.0'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -47,13 +47,11 @@ jobs:
cache-dependency-path: compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ 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
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn workspace babel-plugin-react-compiler lint
# Hardcoded to improve parallelism
@@ -73,9 +71,8 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ 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
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn workspace babel-plugin-react-compiler jest
test:
@@ -99,9 +96,8 @@ jobs:
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ 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
if: steps.node_modules.outputs.cache-hit != 'true'
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'
- run: yarn workspace ${{ matrix.workspace_name }} test

View File

@@ -40,9 +40,7 @@ jobs:
- 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 react-devtools artifacts for base revision
run: |
git fetch origin main
@@ -77,7 +75,6 @@ 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
uses: actions/download-artifact@v4
with:
@@ -137,7 +134,6 @@ jobs:
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
@@ -173,24 +169,14 @@ jobs:
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
- name: Display structure of build
run: ls -R build
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- run: npx playwright install --with-deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: npx playwright install-deps
if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
- name: Playwright install deps
run: |
npx playwright install
sudo npx playwright install-deps
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }}
- run: ls -R build-regression
- run: ./scripts/ci/run_devtools_e2e_tests.js ${{ matrix.version }}

View File

@@ -426,10 +426,6 @@ jobs:
process_artifacts_combined:
name: Process artifacts combined
needs: [build_and_lint, runtime_node_modules_cache]
permissions:
# https://github.com/actions/attest-build-provenance
id-token: write
attestations: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -472,7 +468,6 @@ jobs:
# TODO: Migrate scripts to use `build` directory instead of `build2`
- run: cp ./build.tgz ./build2.tgz
- name: Archive build artifacts
id: upload_artifacts_combined
uses: actions/upload-artifact@v4
with:
name: artifacts_combined
@@ -480,17 +475,6 @@ jobs:
./build.tgz
./build2.tgz
if-no-files-found: error
- uses: actions/attest-build-provenance@v2
# We don't verify builds generated from pull requests not originating from facebook/react.
# However, if the PR lands, the run on `main` will generate the attestation which can then
# be used to download a build via scripts/release/download-experimental-build.js.
#
# Note that this means that scripts/release/download-experimental-build.js must be run with
# --no-verify when downloading a build from a fork.
if: github.event_name == 'push' && github.ref_name == 'main' || github.event.pull_request.head.repo.full_name == github.repository
with:
subject-name: artifacts_combined.zip
subject-digest: sha256:${{ steps.upload_artifacts_combined.outputs.artifact-digest }}
check_error_codes:
name: Search build artifacts for unminified errors
@@ -813,18 +797,14 @@ jobs:
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download artifacts for base revision
# The build could have been generated from a fork, so we must download the build without
# any verification. This is safe since we only use this for sizebot calculation and the
# unverified artifact is not used. Additionally this workflow runs in the pull_request
# trigger so only restricted permissions are available.
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }}) ${{ (github.event.pull_request.head.repo.full_name != github.repository && '--noVerify') || ''}}
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
mv ./build ./base-build
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
- name: Delete extraneous files
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
# quirk of how the release script works. Let's pretend they
# don't exist.
run: rm -rf ./base-build/node_modules
- name: Display structure of base-build from origin/main
run: ls -R base-build

View File

@@ -1,7 +1,7 @@
name: (Runtime) Discord Notify
on:
pull_request_target:
pull_request:
types: [opened, ready_for_review]
paths-ignore:
- compiler/**
@@ -10,19 +10,7 @@ on:
permissions: {}
jobs:
check_access:
runs-on: ubuntu-latest
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
check_maintainer:
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
needs: [check_access]
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer

View File

@@ -46,20 +46,17 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock', 'fixtures/eslint-v*/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
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Install fixture dependencies
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Build plugin
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
run: node build.mjs
- name: Install fixture dependencies
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn --frozen-lockfile
- name: Run lint test
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn lint

View File

@@ -13,16 +13,7 @@ on:
dist_tag:
required: true
type: string
enableFailureNotification:
description: 'Whether to notify the team on Discord when the release fails. Useful if this workflow is called from an automation.'
required: false
type: boolean
secrets:
DISCORD_WEBHOOK_URL:
description: 'Discord webhook URL to notify on failure. Only required if enableFailureNotification is true.'
required: false
GH_TOKEN:
required: true
NPM_TOKEN:
required: true
@@ -38,9 +29,6 @@ jobs:
publish_prerelease:
name: Publish prelease (${{ inputs.release_channel }}) ${{ inputs.commit_sha }} @${{ inputs.dist_tag }}
runs-on: ubuntu-latest
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -58,18 +46,8 @@ jobs:
- 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'
- run: |
GH_TOKEN=${{ secrets.GH_TOKEN }} scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }}
- name: Notify Discord on failure
if: failure() && inputs.enableFailureNotification == true
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: "GitHub Actions"
embed-title: 'Publish of $${{ inputs.release_channel }} release failed'
embed-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}

View File

@@ -16,9 +16,6 @@ jobs:
publish_prerelease_canary:
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
with:
commit_sha: ${{ inputs.prerelease_commit_sha }}
release_channel: stable
@@ -35,14 +32,10 @@ jobs:
dist_tag: canary,next
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
@@ -54,4 +47,3 @@ jobs:
dist_tag: experimental
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -14,25 +14,16 @@ jobs:
publish_prerelease_canary:
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
with:
commit_sha: ${{ github.sha }}
release_channel: stable
dist_tag: canary,next
enableFailureNotification: true
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_prerelease_experimental:
name: Publish to Experimental channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
# NOTE: Intentionally running these jobs sequentially because npm
# will sometimes fail if you try to concurrently publish two
# different versions of the same package, even if they use different
@@ -42,8 +33,5 @@ jobs:
commit_sha: ${{ github.sha }}
release_channel: experimental
dist_tag: experimental
enableFailureNotification: true
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -78,9 +78,7 @@ jobs:
- 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'
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- if: '${{ inputs.only_packages }}'
name: 'Prepare ${{ inputs.only_packages }} from NPM'

View File

@@ -6,6 +6,10 @@ on:
actor:
required: true
type: string
is_remote:
required: false
type: boolean
default: false
outputs:
is_core_team:
value: ${{ jobs.check_maintainer.outputs.is_core_team }}
@@ -26,6 +30,7 @@ jobs:
outputs:
is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }}
steps:
- uses: actions/checkout@v4
- name: Check if actor is maintainer
id: check_if_actor_is_maintainer
uses: actions/github-script@v7
@@ -33,20 +38,33 @@ jobs:
script: |
const fs = require('fs');
const actor = '${{ inputs.actor }}';
const res = await github.rest.repos.getContent({
owner: 'facebook',
repo: 'react',
path: 'MAINTAINERS',
ref: 'main',
headers: { Accept: 'application/vnd.github+json' }
});
if (res.status !== 200) {
console.error(res);
throw new Error('Unable to fetch MAINTAINERS file');
let isRemote = ${{ inputs.is_remote }};
if (typeof isRemote === 'string') {
isRemote = isRemote === 'true';
}
content = Buffer.from(res.data.content, 'base64').toString();
if (content == null || typeof content !== 'string') {
throw new Error('Unable to retrieve MAINTAINERS file');
if (typeof isRemote !== 'boolean') {
throw new Error(`Invalid \`isRemote\` input. Expected a boolean, got: ${isRemote}`);
}
let content = null;
if (isRemote === true) {
const res = await github.rest.repos.getContent({
owner: 'facebook',
repo: 'react',
path: 'MAINTAINERS',
ref: 'main',
headers: { Accept: 'application/vnd.github+json' }
});
if (res.status !== 200) {
console.error(res);
throw new Error('Unable to fetch MAINTAINERS file');
}
content = Buffer.from(res.data.content, 'base64').toString();
} else {
content = await fs.readFileSync('./MAINTAINERS', { encoding: 'utf8' });
}
if (content === null) {
throw new Error('Unable to retrieve local or http MAINTAINERS file');
}
const maintainers = new Set(content.split('\n'));

View File

@@ -3,8 +3,7 @@
name: (Shared) Cleanup Stale Branch Caches
on:
schedule:
# Every 6 hours
- cron: 0 */6 * * *
- cron: 0 0 * * *
workflow_dispatch:
permissions: {}

View File

@@ -1,8 +1,7 @@
name: (Shared) Label Core Team PRs
on:
pull_request_target:
types: [opened]
pull_request:
permissions: {}
@@ -12,19 +11,7 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
check_access:
runs-on: ubuntu-latest
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}
run: echo "is_member_or_collaborator=true" >> "$GITHUB_OUTPUT"
check_maintainer:
if: ${{ needs.check_access.outputs.is_member_or_collaborator == 'true' || needs.check_access.outputs.is_member_or_collaborator == true }}
needs: [check_access]
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer

View File

@@ -29,7 +29,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -37,7 +36,6 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn prettier-check
eslint:
@@ -52,7 +50,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -60,7 +57,6 @@ jobs:
- 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/eslint
check_license:
@@ -75,7 +71,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -83,7 +78,6 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/check_license.sh
test_print_warnings:
@@ -98,7 +92,6 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
@@ -106,5 +99,4 @@ jobs:
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/test_print_warnings.sh

2
.nvmrc
View File

@@ -1 +1 @@
v20.19.0
v18.20.1

18
CHANGELOG-canary.md Normal file
View File

@@ -0,0 +1,18 @@
## March 22, 2024 (18.3.0-canary-670811593-20240322)
## React
- Added `useActionState` to replace `useFormState` and added `pending` value ([#28491](https://github.com/facebook/react/pull/28491)).
## October 5, 2023 (18.3.0-canary-546178f91-20231005)
### React
- Added support for async functions to be passed to `startTransition`.
- `useTransition` now triggers the nearest error boundary instead of a global error.
- Added `useOptimistic`, a new Hook for handling optimistic UI updates. It optimistically updates the UI before receiving confirmation from a server or external source.
### React DOM
- Added support for passing async functions to the `action` prop on `<form>`. When the function passed to `action` is marked with [`'use server'`](https://react.dev/reference/react/use-server), the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement).
- Added `useFormStatus`, a new Hook for checking the submission state of a form.
- Added `useFormState`, a new Hook for updating state upon form submission. When the function passed to `useFormState` is marked with [`'use server'`](https://react.dev/reference/react/use-server), the update is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement).

View File

@@ -1,50 +1,3 @@
## 19.1.0 (March 28, 2025)
### Owner Stack
An Owner Stack is a string representing the components that are directly responsible for rendering a particular component. You can log Owner Stacks when debugging or use Owner Stacks to enhance error overlays or other development tools. Owner Stacks are only available in development builds. Component Stacks in production are unchanged.
* An Owner Stack is a development-only stack trace that helps identify which components are responsible for rendering a particular component. An Owner Stack is distinct from a Component Stacks, which shows the hierarchy of components leading to an error.
* The [captureOwnerStack API](https://react.dev/reference/react/captureOwnerStack) is only available in development mode and returns a Owner Stack, if available. The API can be used to enhance error overlays or log component relationships when debugging. [#29923](https://github.com/facebook/react/pull/29923), [#32353](https://github.com/facebook/react/pull/32353), [#30306](https://github.com/facebook/react/pull/30306),
[#32538](https://github.com/facebook/react/pull/32538), [#32529](https://github.com/facebook/react/pull/32529), [#32538](https://github.com/facebook/react/pull/32538)
### React
* Enhanced support for Suspense boundaries to be used anywhere, including the client, server, and during hydration. [#32069](https://github.com/facebook/react/pull/32069), [#32163](https://github.com/facebook/react/pull/32163), [#32224](https://github.com/facebook/react/pull/32224), [#32252](https://github.com/facebook/react/pull/32252)
* Reduced unnecessary client rendering through improved hydration scheduling [#31751](https://github.com/facebook/react/pull/31751)
* Increased priority of client rendered Suspense boundaries [#31776](https://github.com/facebook/react/pull/31776)
* Fixed frozen fallback states by rendering unfinished Suspense boundaries on the client. [#31620](https://github.com/facebook/react/pull/31620)
* Reduced garbage collection pressure by improving Suspense boundary retries. [#31667](https://github.com/facebook/react/pull/31667)
* Fixed erroneous “Waiting for Paint” log when the passive effect phase was not delayed [#31526](https://github.com/facebook/react/pull/31526)
* Fixed a regression causing key warnings for flattened positional children in development mode. [#32117](https://github.com/facebook/react/pull/32117)
* Updated `useId` to use valid CSS selectors, changing format from `:r123:` to `«r123»`. [#32001](https://github.com/facebook/react/pull/32001)
* Added a dev-only warning for null/undefined created in useEffect, useInsertionEffect, and useLayoutEffect. [#32355](https://github.com/facebook/react/pull/32355)
* Fixed a bug where dev-only methods were exported in production builds. React.act is no longer available in production builds. [#32200](https://github.com/facebook/react/pull/32200)
* Improved consistency across prod and dev to improve compatibility with Google Closure Complier and bindings [#31808](https://github.com/facebook/react/pull/31808)
* Improve passive effect scheduling for consistent task yielding. [#31785](https://github.com/facebook/react/pull/31785)
* Fixed asserts in React Native when passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent rendering. [#32528](https://github.com/facebook/react/pull/32528)
* Fixed component name resolution for Portal [#32640](https://github.com/facebook/react/pull/32640)
* Added support for beforetoggle and toggle events on the dialog element. #32479 [#32479](https://github.com/facebook/react/pull/32479)
### React DOM
* Fixed double warning when the `href` attribute is an empty string [#31783](https://github.com/facebook/react/pull/31783)
* Fixed an edge case where `getHoistableRoot()` didnt work properly when the container was a Document [#32321](https://github.com/facebook/react/pull/32321)
* Removed support for using HTML comments (e.g. `<!-- -->`) as a DOM container. [#32250](https://github.com/facebook/react/pull/32250)
* Added support for `<script>` and `<template>` tags to be nested within `<select>` tags. [#31837](https://github.com/facebook/react/pull/31837)
* Fixed responsive images to be preloaded as HTML instead of headers [#32445](https://github.com/facebook/react/pull/32445)
### use-sync-external-store
* Added `exports` field to `package.json` for `use-sync-external-store` to support various entrypoints. [#25231](https://github.com/facebook/react/pull/25231)
### React Server Components
* Added `unstable_prerender`, a new experimental API for prerendering React Server Components on the server [#31724](https://github.com/facebook/react/pull/31724)
* Fixed an issue where streams would hang when receiving new chunks after a global error [#31840](https://github.com/facebook/react/pull/31840), [#31851](https://github.com/facebook/react/pull/31851)
* Fixed an issue where pending chunks were counted twice. [#31833](https://github.com/facebook/react/pull/31833)
* Added support for streaming in edge environments [#31852](https://github.com/facebook/react/pull/31852)
* Added support for sending custom error names from a server so that they are available in the client for console replaying. [#32116](https://github.com/facebook/react/pull/32116)
* Updated the server component wire format to remove IDs for hints and console.log because they have no return value [#31671](https://github.com/facebook/react/pull/31671)
* Exposed `registerServerReference` in client builds to handle server references in different environments. [#32534](https://github.com/facebook/react/pull/32534)
* Added react-server-dom-parcel package which integrates Server Components with the [Parcel bundler](https://parceljs.org/) [#31725](https://github.com/facebook/react/pull/31725), [#32132](https://github.com/facebook/react/pull/32132), [#31799](https://github.com/facebook/react/pull/31799), [#32294](https://github.com/facebook/react/pull/32294), [#31741](https://github.com/facebook/react/pull/31741)
## 19.0.0 (December 5, 2024)
Below is a list of all new features, APIs, deprecations, and breaking changes. Read [React 19 release post](https://react.dev/blog/2024/04/25/react-19) and [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for more information.

View File

@@ -18,7 +18,7 @@
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.2.0';
const ReactVersion = '19.1.0';
// The label used by the @canary channel. Represents the upcoming release's
// stability. Most of the time, this will be "canary", but we may temporarily
@@ -33,7 +33,7 @@ const canaryChannelLabel = 'canary';
const rcNumber = 0;
const stablePackages = {
'eslint-plugin-react-hooks': '6.1.0',
'eslint-plugin-react-hooks': '5.2.0',
'jest-react': '0.17.0',
react: ReactVersion,
'react-art': ReactVersion,
@@ -42,12 +42,12 @@ const stablePackages = {
'react-server-dom-turbopack': ReactVersion,
'react-server-dom-parcel': ReactVersion,
'react-is': ReactVersion,
'react-reconciler': '0.33.0',
'react-refresh': '0.18.0',
'react-reconciler': '0.32.0',
'react-refresh': '0.17.0',
'react-test-renderer': ReactVersion,
'use-subscription': '1.12.0',
'use-sync-external-store': '1.6.0',
scheduler: '0.27.0',
'use-subscription': '1.11.0',
'use-sync-external-store': '1.5.0',
scheduler: '0.26.0',
};
// These packages do not exist in the @canary or @latest channel, only

View File

@@ -1,59 +0,0 @@
## 19.1.0-rc.1 (April 21, 2025)
## eslint-plugin-react-hooks
* Temporarily disable ref access in render validation [#32839](https://github.com/facebook/react/pull/32839) by [@poteto](https://github.com/poteto)
* Fix type error with recommended config [#32666](https://github.com/facebook/react/pull/32666) by [@niklasholm](https://github.com/niklasholm)
* Merge rule from eslint-plugin-react-compiler into `react-hooks` plugin [#32416](https://github.com/facebook/react/pull/32416) by [@michaelfaith](https://github.com/michaelfaith)
* Add dev dependencies for typescript migration [#32279](https://github.com/facebook/react/pull/32279) by [@michaelfaith](https://github.com/michaelfaith)
* Support v9 context api [#32045](https://github.com/facebook/react/pull/32045) by [@michaelfaith](https://github.com/michaelfaith)
* Support eslint 8+ flat plugin syntax out of the box for eslint-plugin-react-compiler [#32120](https://github.com/facebook/react/pull/32120) by [@orta](https://github.com/orta)
## babel-plugin-react-compiler
* Support satisfies operator [#32742](https://github.com/facebook/react/pull/32742) by [@rodrigofariow](https://github.com/rodrigofariow)
* Fix inferEffectDependencies lint false positives [#32769](https://github.com/facebook/react/pull/32769) by [@mofeiZ](https://github.com/mofeiZ)
* Fix hoisting of let declarations [#32724](https://github.com/facebook/react/pull/32724) by [@mofeiZ](https://github.com/mofeiZ)
* Avoid failing builds when import specifiers conflict or shadow vars [#32663](https://github.com/facebook/react/pull/32663) by [@mofeiZ](https://github.com/mofeiZ)
* Optimize components declared with arrow function and implicit return and `compilationMode: 'infer'` [#31792](https://github.com/facebook/react/pull/31792) by [@dimaMachina](https://github.com/dimaMachina)
* Validate static components [#32683](https://github.com/facebook/react/pull/32683) by [@josephsavona](https://github.com/josephsavona)
* Hoist dependencies from functions more conservatively [#32616](https://github.com/facebook/react/pull/32616) by [@mofeiZ](https://github.com/mofeiZ)
* Implement NumericLiteral as ObjectPropertyKey [#31791](https://github.com/facebook/react/pull/31791) by [@dimaMachina](https://github.com/dimaMachina)
* Avoid bailouts when inserting gating [#32598](https://github.com/facebook/react/pull/32598) by [@mofeiZ](https://github.com/mofeiZ)
* Stop bailing out early for hoisted gated functions [#32597](https://github.com/facebook/react/pull/32597) by [@mofeiZ](https://github.com/mofeiZ)
* Add shape for Array.from [#32522](https://github.com/facebook/react/pull/32522) by [@mofeiZ](https://github.com/mofeiZ)
* Patch array and argument spread mutability [#32521](https://github.com/facebook/react/pull/32521) by [@mofeiZ](https://github.com/mofeiZ)
* Make CompilerError compatible with reflection [#32539](https://github.com/facebook/react/pull/32539) by [@poteto](https://github.com/poteto)
* Add simple walltime measurement [#32331](https://github.com/facebook/react/pull/32331) by [@poteto](https://github.com/poteto)
* Improve error messages for unhandled terminal and instruction kinds [#32324](https://github.com/facebook/react/pull/32324) by [@inottn](https://github.com/inottn)
* Handle TSInstantiationExpression in lowerExpression [#32302](https://github.com/facebook/react/pull/32302) by [@inottn](https://github.com/inottn)
* Fix invalid Array.map type [#32095](https://github.com/facebook/react/pull/32095) by [@mofeiZ](https://github.com/mofeiZ)
* Patch for JSX escape sequences in @babel/generator [#32131](https://github.com/facebook/react/pull/32131) by [@mofeiZ](https://github.com/mofeiZ)
* `JSXText` emits incorrect with bracket [#32138](https://github.com/facebook/react/pull/32138) by [@himself65](https://github.com/himself65)
* Validation against calling impure functions [#31960](https://github.com/facebook/react/pull/31960) by [@josephsavona](https://github.com/josephsavona)
* Always target node [#32091](https://github.com/facebook/react/pull/32091) by [@poteto](https://github.com/poteto)
* Patch compilationMode:infer object method edge case [#32055](https://github.com/facebook/react/pull/32055) by [@mofeiZ](https://github.com/mofeiZ)
* Generate ts defs [#31994](https://github.com/facebook/react/pull/31994) by [@poteto](https://github.com/poteto)
* Relax react peer dep requirement [#31915](https://github.com/facebook/react/pull/31915) by [@poteto](https://github.com/poteto)
* Allow type cast expressions with refs [#31871](https://github.com/facebook/react/pull/31871) by [@josephsavona](https://github.com/josephsavona)
* Add shape for global Object.keys [#31583](https://github.com/facebook/react/pull/31583) by [@mofeiZ](https://github.com/mofeiZ)
* Optimize method calls w props receiver [#31775](https://github.com/facebook/react/pull/31775) by [@josephsavona](https://github.com/josephsavona)
* Fix dropped ref with spread props in InlineJsxTransform [#31726](https://github.com/facebook/react/pull/31726) by [@jackpope](https://github.com/jackpope)
* Support for non-declatation for in/of iterators [#31710](https://github.com/facebook/react/pull/31710) by [@mvitousek](https://github.com/mvitousek)
* Support for context variable loop iterators [#31709](https://github.com/facebook/react/pull/31709) by [@mvitousek](https://github.com/mvitousek)
* Replace deprecated dependency in `eslint-plugin-react-compiler` [#31629](https://github.com/facebook/react/pull/31629) by [@rakleed](https://github.com/rakleed)
* Support enableRefAsProp in jsx transform [#31558](https://github.com/facebook/react/pull/31558) by [@jackpope](https://github.com/jackpope)
* Fix: ref.current now correctly reactive [#31521](https://github.com/facebook/react/pull/31521) by [@mofeiZ](https://github.com/mofeiZ)
* Outline JSX with non-jsx children [#31442](https://github.com/facebook/react/pull/31442) by [@gsathya](https://github.com/gsathya)
* Outline jsx with duplicate attributes [#31441](https://github.com/facebook/react/pull/31441) by [@gsathya](https://github.com/gsathya)
* Store original and new prop names [#31440](https://github.com/facebook/react/pull/31440) by [@gsathya](https://github.com/gsathya)
* Stabilize compiler output: sort deps and decls by name [#31362](https://github.com/facebook/react/pull/31362) by [@mofeiZ](https://github.com/mofeiZ)
* Bugfix for hoistable deps for nested functions [#31345](https://github.com/facebook/react/pull/31345) by [@mofeiZ](https://github.com/mofeiZ)
* Remove compiler runtime-compat fixture library [#31430](https://github.com/facebook/react/pull/31430) by [@poteto](https://github.com/poteto)
* Wrap inline jsx transform codegen in conditional [#31267](https://github.com/facebook/react/pull/31267) by [@jackpope](https://github.com/jackpope)
* Check if local identifier is a hook when resolving globals [#31384](https://github.com/facebook/react/pull/31384) by [@poteto](https://github.com/poteto)
* Handle member expr as computed property [#31344](https://github.com/facebook/react/pull/31344) by [@gsathya](https://github.com/gsathya)
* Fix to ref access check to ban ref?.current [#31360](https://github.com/facebook/react/pull/31360) by [@mvitousek](https://github.com/mvitousek)
* InlineJSXTransform transforms jsx inside function expressions [#31282](https://github.com/facebook/react/pull/31282) by [@josephsavona](https://github.com/josephsavona)
## Other
* Add shebang to banner [#32225](https://github.com/facebook/react/pull/32225) by [@Jeremy-Hibiki](https://github.com/Jeremy-Hibiki)
* remove terser from react-compiler-runtime build [#31326](https://github.com/facebook/react/pull/31326) by [@henryqdineen](https://github.com/henryqdineen)

View File

@@ -19,9 +19,7 @@ import BabelPluginReactCompiler, {
PluginOptions,
CompilerPipelineValue,
parsePluginOptions,
printReactiveFunctionWithOutlined,
printFunctionWithOutlined,
} from 'babel-plugin-react-compiler';
} from 'babel-plugin-react-compiler/src';
import clsx from 'clsx';
import invariant from 'invariant';
import {useSnackbar} from 'notistack';
@@ -43,6 +41,8 @@ import {
default as Output,
PrintedCompilerPipelineValue,
} from './Output';
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
import {transformFromAstSync} from '@babel/core';
function parseInput(

View File

@@ -6,7 +6,7 @@
*/
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import {CompilerErrorDetail} from 'babel-plugin-react-compiler';
import {CompilerErrorDetail} from 'babel-plugin-react-compiler/src';
import invariant from 'invariant';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';

View File

@@ -11,7 +11,7 @@ import {
InformationCircleIcon,
} from '@heroicons/react/outline';
import MonacoEditor, {DiffEditor} from '@monaco-editor/react';
import {type CompilerError} from 'babel-plugin-react-compiler';
import {type CompilerError} from 'babel-plugin-react-compiler/src';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';

View File

@@ -6,7 +6,10 @@
*/
import {Monaco} from '@monaco-editor/react';
import {CompilerErrorDetail, ErrorSeverity} from 'babel-plugin-react-compiler';
import {
CompilerErrorDetail,
ErrorSeverity,
} from 'babel-plugin-react-compiler/src';
import {MarkerSeverity, type editor} from 'monaco-editor';
function mapReactCompilerSeverityToMonaco(
@@ -51,7 +54,7 @@ export function renderReactCompilerMarkers({
model,
details,
}: ReactCompilerMarkerConfig): void {
const markers: Array<editor.IMarkerData> = [];
let markers = [];
for (const detail of details) {
const marker = mapReactCompilerDiagnosticToMonacoMarker(detail, monaco);
if (marker == null) {

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run watch\" \"yarn workspace react-compiler-runtime run watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build --dts\" \"yarn workspace react-compiler-runtime run build\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
"build": "yarn build:compiler && next build",
"postbuild": "node ./scripts/downloadFonts.js",
"preinstall": "cd ../.. && yarn install --frozen-lockfile",
@@ -27,7 +27,7 @@
"@babel/types": "7.26.3",
"@heroicons/react": "^1.0.6",
"@monaco-editor/react": "^4.4.6",
"@playwright/test": "^1.51.1",
"@playwright/test": "^1.42.1",
"@use-gesture/react": "^10.2.22",
"hermes-eslint": "^0.25.0",
"hermes-parser": "^0.25.0",

View File

@@ -781,12 +781,12 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
"@playwright/test@^1.51.1":
version "1.51.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.51.1.tgz#75357d513221a7be0baad75f01e966baf9c41a2e"
integrity sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==
"@playwright/test@^1.42.1":
version "1.47.2"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.47.2.tgz#dbe7051336bfc5cc599954214f9111181dbc7475"
integrity sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==
dependencies:
playwright "1.51.1"
playwright "1.47.2"
"@rtsao/scc@^1.1.0":
version "1.1.0"
@@ -1249,14 +1249,14 @@ camelcase-css@^2.0.1:
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001579:
version "1.0.30001715"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz"
integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==
version "1.0.30001669"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3"
integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==
caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663:
version "1.0.30001715"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz"
integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==
version "1.0.30001664"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4"
integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==
chalk@^2.4.2:
version "2.4.2"
@@ -3008,17 +3008,17 @@ pirates@^4.0.1:
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
playwright-core@1.51.1:
version "1.51.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.51.1.tgz#d57f0393e02416f32a47cf82b27533656a8acce1"
integrity sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==
playwright-core@1.47.2:
version "1.47.2"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.47.2.tgz#7858da9377fa32a08be46ba47d7523dbd9460a4e"
integrity sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==
playwright@1.51.1:
version "1.51.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.51.1.tgz#ae1467ee318083968ad28d6990db59f47a55390f"
integrity sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==
playwright@1.47.2:
version "1.47.2"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.47.2.tgz#155688aa06491ee21fb3e7555b748b525f86eb20"
integrity sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==
dependencies:
playwright-core "1.51.1"
playwright-core "1.47.2"
optionalDependencies:
fsevents "2.3.2"

View File

@@ -37,7 +37,7 @@
"prettier": "^3.3.3",
"prettier-plugin-hermes-parser": "^0.26.0",
"prompt-promise": "^1.0.3",
"rimraf": "^6.0.1",
"rimraf": "^5.0.10",
"to-fast-properties": "^2.0.0",
"tsup": "^8.4.0",
"typescript": "^5.4.3",
@@ -45,6 +45,7 @@
"yargs": "^17.7.2"
},
"resolutions": {
"rimraf": "5.0.10",
"@babel/types": "7.26.3"
},
"packageManager": "yarn@1.22.22"

View File

@@ -12,12 +12,12 @@
"build": "rimraf dist && tsup",
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
"jest": "yarn build && ts-node node_modules/.bin/jest",
"snap": "yarn workspace snap run snap",
"snap": "node ../snap/dist/main.js",
"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",
"watch": "yarn build --dts --watch"
"watch": "yarn build --watch"
},
"dependencies": {
"@babel/types": "^7.26.0"

View File

@@ -73,7 +73,7 @@ export default function BabelPluginReactCompiler(
pass.filename ?? null,
opts.logger,
opts.environment,
result,
result?.retryErrors ?? [],
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {

View File

@@ -182,9 +182,7 @@ export type LoggerEvent =
| CompileDiagnosticEvent
| CompileSkipEvent
| PipelineErrorEvent
| TimingEvent
| AutoDepsDecorationsEvent
| AutoDepsEligibleEvent;
| TimingEvent;
export type CompileErrorEvent = {
kind: 'CompileError';
@@ -221,16 +219,6 @@ export type TimingEvent = {
kind: 'Timing';
measurement: PerformanceMeasure;
};
export type AutoDepsDecorationsEvent = {
kind: 'AutoDepsDecorations';
fnLoc: t.SourceLocation;
decorations: Array<t.SourceLocation>;
};
export type AutoDepsEligibleEvent = {
kind: 'AutoDepsEligible';
fnLoc: t.SourceLocation;
depArrayLoc: t.SourceLocation;
};
export type Logger = {
logEvent: (filename: string | null, event: LoggerEvent) => void;

View File

@@ -392,11 +392,6 @@ function runWithEnvironment(
if (env.config.inferEffectDependencies) {
inferEffectDependencies(hir);
log({
kind: 'hir',
name: 'InferEffectDependencies',
value: hir,
});
}
if (env.config.inlineJsxTransform) {

View File

@@ -30,7 +30,6 @@ import {
findProgramSuppressions,
suppressionsToCompilerError,
} from './Suppression';
import {GeneratedSource} from '../HIR';
export type CompilerPass = {
opts: PluginOptions;
@@ -268,9 +267,8 @@ function isFilePartOfSources(
return false;
}
export type CompileProgramResult = {
type CompileProgramResult = {
retryErrors: Array<{fn: BabelFn; error: CompilerError}>;
inferredEffectLocations: Set<t.SourceLocation>;
};
/**
* `compileProgram` is directly invoked by the react-compiler babel plugin, so
@@ -371,7 +369,6 @@ export function compileProgram(
},
);
const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
const inferredEffectLocations = new Set<t.SourceLocation>();
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType,
@@ -469,23 +466,6 @@ export function compileProgram(
}
}
/**
* Otherwise if 'use no forget/memo' is present, we still run the code through the compiler
* for validation but we don't mutate the babel AST. This allows us to flag if there is an
* unused 'use no forget/memo' directive.
*/
if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) {
for (const directive of optOutDirectives) {
pass.opts.logger?.logEvent(pass.filename, {
kind: 'CompileSkip',
fnLoc: fn.node.body.loc ?? null,
reason: `Skipped due to '${directive.value.value}' directive.`,
loc: directive.loc ?? null,
});
}
return null;
}
pass.opts.logger?.logEvent(pass.filename, {
kind: 'CompileSuccess',
fnLoc: fn.node.loc ?? null,
@@ -509,17 +489,26 @@ export function compileProgram(
return null;
}
/**
* Otherwise if 'use no forget/memo' is present, we still run the code through the compiler
* for validation but we don't mutate the babel AST. This allows us to flag if there is an
* unused 'use no forget/memo' directive.
*/
if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) {
for (const directive of optOutDirectives) {
pass.opts.logger?.logEvent(pass.filename, {
kind: 'CompileSkip',
fnLoc: fn.node.body.loc ?? null,
reason: `Skipped due to '${directive.value.value}' directive.`,
loc: directive.loc ?? null,
});
}
return null;
}
if (!pass.opts.noEmit) {
return compileResult.compiledFn;
}
/**
* inferEffectDependencies + noEmit is currently only used for linting. In
* this mode, add source locations for where the compiler *can* infer effect
* dependencies.
*/
for (const loc of compileResult.compiledFn.inferredEffectLocations) {
if (loc !== GeneratedSource) inferredEffectLocations.add(loc);
}
return null;
};
@@ -598,7 +587,7 @@ export function compileProgram(
if (compiledFns.length > 0) {
addImportsToProgram(program, programContext);
}
return {retryErrors, inferredEffectLocations};
return {retryErrors};
}
function shouldSkipCompilation(

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import type * as BabelCore from '@babel/core';
import {hasOwnProperty} from '../Utils/utils';
import {PluginOptions} from './Options';

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
@@ -18,7 +11,6 @@ import {
import {getOrInsertWith} from '../Utils/utils';
import {Environment} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {CompileProgramResult} from './Program';
function throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
@@ -44,16 +36,12 @@ function assertValidEffectImportReference(
const parent = path.parentPath;
if (parent != null && parent.isCallExpression()) {
const args = parent.get('arguments');
const maybeCalleeLoc = path.node.loc;
const hasInferredEffect =
maybeCalleeLoc != null &&
context.inferredEffectLocations.has(maybeCalleeLoc);
/**
* Only error on untransformed references of the form `useMyEffect(...)`
* or `moduleNamespace.useMyEffect(...)`, with matching argument counts.
* TODO: do we also want a mode to also hard error on non-call references?
*/
if (args.length === numArgs && !hasInferredEffect) {
if (args.length === numArgs) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
path,
context.transformErrors,
@@ -109,7 +97,7 @@ export default function validateNoUntransformedReferences(
filename: string | null,
logger: Logger | null,
env: EnvironmentConfig,
compileResult: CompileProgramResult | null,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const moduleLoadChecks = new Map<
string,
@@ -138,7 +126,7 @@ export default function validateNoUntransformedReferences(
}
}
if (moduleLoadChecks.size > 0) {
transformProgram(path, moduleLoadChecks, filename, logger, compileResult);
transformProgram(path, moduleLoadChecks, filename, logger, transformErrors);
}
}
@@ -148,7 +136,6 @@ type TraversalState = {
logger: Logger | null;
filename: string | null;
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>;
inferredEffectLocations: Set<t.SourceLocation>;
};
type CheckInvalidReferenceFn = (
paths: Array<NodePath<t.Node>>,
@@ -236,16 +223,14 @@ function transformProgram(
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
filename: string | null,
logger: Logger | null,
compileResult: CompileProgramResult | null,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): void {
const traversalState: TraversalState = {
shouldInvalidateScopes: true,
program: path,
filename,
logger,
transformErrors: compileResult?.retryErrors ?? [],
inferredEffectLocations:
compileResult?.inferredEffectLocations ?? new Set(),
transformErrors,
};
path.traverse({
ImportDeclaration(path: NodePath<t.ImportDeclaration>) {

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {CompilerError} from '..';
import {
BlockId,

View File

@@ -2406,19 +2406,6 @@ function lowerExpression(
kind: 'TypeCastExpression',
value: lowerExpressionToTemporary(builder, expr.get('expression')),
typeAnnotation: typeAnnotation.node,
typeAnnotationKind: 'cast',
type: lowerType(typeAnnotation.node),
loc: exprLoc,
};
}
case 'TSSatisfiesExpression': {
let expr = exprPath as NodePath<t.TSSatisfiesExpression>;
const typeAnnotation = expr.get('typeAnnotation');
return {
kind: 'TypeCastExpression',
value: lowerExpressionToTemporary(builder, expr.get('expression')),
typeAnnotation: typeAnnotation.node,
typeAnnotationKind: 'satisfies',
type: lowerType(typeAnnotation.node),
loc: exprLoc,
};
@@ -2430,7 +2417,6 @@ function lowerExpression(
kind: 'TypeCastExpression',
value: lowerExpressionToTemporary(builder, expr.get('expression')),
typeAnnotation: typeAnnotation.node,
typeAnnotationKind: 'as',
type: lowerType(typeAnnotation.node),
loc: exprLoc,
};

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {CompilerError} from '../CompilerError';
import {getScopes, recursivelyTraverseItems} from './AssertValidBlockNesting';
import {Environment} from './Environment';

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {CompilerError} from '../CompilerError';
import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables';
import {printDependency} from '../ReactiveScopes/PrintReactiveFunction';
@@ -19,7 +12,6 @@ import {
BasicBlock,
BlockId,
DependencyPathEntry,
FunctionExpression,
GeneratedSource,
getHookKind,
HIRFunction,
@@ -31,7 +23,6 @@ import {
PropertyLiteral,
ReactiveScopeDependency,
ScopeId,
TInstruction,
} from './HIR';
const DEBUG_PRINT = false;
@@ -129,33 +120,6 @@ export function collectHoistablePropertyLoads(
});
}
export function collectHoistablePropertyLoadsInInnerFn(
fnInstr: TInstruction<FunctionExpression>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
): ReadonlyMap<BlockId, BlockInfo> {
const fn = fnInstr.value.loweredFunc.func;
const initialContext: CollectHoistablePropertyLoadsContext = {
temporaries,
knownImmutableIdentifiers: new Set(),
hoistableFromOptionals,
registry: new PropertyPathRegistry(),
nestedFnImmutableContext: null,
assumedInvokedFns: fn.env.config.enableTreatFunctionDepsAsConditional
? new Set()
: getAssumedInvokedFunctions(fn),
};
const nestedFnImmutableContext = new Set(
fn.context
.filter(place =>
isImmutableAtInstr(place.identifier, fnInstr.id, initialContext),
)
.map(place => place.identifier.id),
);
initialContext.nestedFnImmutableContext = nestedFnImmutableContext;
return collectHoistablePropertyLoadsImpl(fn, initialContext);
}
type CollectHoistablePropertyLoadsContext = {
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
knownImmutableIdentifiers: ReadonlySet<IdentifierId>;

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {CompilerError} from '..';
import {assertNonNull} from './CollectHoistablePropertyLoads';
import {

View File

@@ -11,7 +11,6 @@ import {fromZodError} from 'zod-validation-error';
import {CompilerError} from '../CompilerError';
import {
CompilationMode,
defaultOptions,
Logger,
PanicThresholdOptions,
parsePluginOptions,
@@ -780,7 +779,6 @@ export function parseConfigPragmaForTests(
const environment = parseConfigPragmaEnvironmentForTest(pragma);
let compilationMode: CompilationMode = defaults.compilationMode;
let panicThreshold: PanicThresholdOptions = 'all_errors';
let noEmit: boolean = defaultOptions.noEmit;
for (const token of pragma.split(' ')) {
if (!token.startsWith('@')) {
continue;
@@ -806,17 +804,12 @@ export function parseConfigPragmaForTests(
panicThreshold = 'none';
break;
}
case '@noEmit': {
noEmit = true;
break;
}
}
}
return parsePluginOptions({
environment,
compilationMode,
panicThreshold,
noEmit,
});
}
@@ -859,7 +852,6 @@ export class Environment {
programContext: ProgramContext;
hasFireRewrite: boolean;
hasInferredEffect: boolean;
inferredEffectLocations: Set<SourceLocation> = new Set();
#contextIdentifiers: Set<t.Identifier>;
#hoistedIdentifiers: Set<t.Identifier>;

View File

@@ -9,7 +9,6 @@ import {Effect, ValueKind, ValueReason} from './HIR';
import {
BUILTIN_SHAPES,
BuiltInArrayId,
BuiltInFireFunctionId,
BuiltInFireId,
BuiltInMapId,
BuiltInMixedReadonlyId,
@@ -675,12 +674,7 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
{
positionalParams: [],
restParam: null,
returnType: {
kind: 'Function',
return: {kind: 'Poly'},
shapeId: BuiltInFireFunctionId,
isConstructor: false,
},
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Frozen,
},

View File

@@ -746,6 +746,27 @@ export enum InstructionKind {
Function = 'Function',
}
export function convertHoistedLValueKind(
kind: InstructionKind,
): InstructionKind | null {
switch (kind) {
case InstructionKind.HoistedLet:
return InstructionKind.Let;
case InstructionKind.HoistedConst:
return InstructionKind.Const;
case InstructionKind.HoistedFunction:
return InstructionKind.Function;
case InstructionKind.Let:
case InstructionKind.Const:
case InstructionKind.Function:
case InstructionKind.Reassign:
case InstructionKind.Catch:
return null;
default:
assertExhaustive(kind, 'Unexpected lvalue kind');
}
}
function _staticInvariantInstructionValueHasLocation(
value: InstructionValue,
): SourceLocation {
@@ -910,21 +931,13 @@ export type InstructionValue =
value: Place;
loc: SourceLocation;
}
| ({
| {
kind: 'TypeCastExpression';
value: Place;
typeAnnotation: t.FlowType | t.TSType;
type: Type;
loc: SourceLocation;
} & (
| {
typeAnnotation: t.FlowType;
typeAnnotationKind: 'cast';
}
| {
typeAnnotation: t.TSType;
typeAnnotationKind: 'as' | 'satisfies';
}
))
}
| JsxExpression
| {
kind: 'ObjectExpression';
@@ -1722,12 +1735,6 @@ export function isDispatcherType(id: Identifier): boolean {
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInDispatch';
}
export function isFireFunctionType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInFireFunction'
);
}
export function isStableType(id: Identifier): boolean {
return (
isSetStateType(id) ||
@@ -1738,40 +1745,6 @@ export function isStableType(id: Identifier): boolean {
);
}
export function isStableTypeContainer(id: Identifier): boolean {
const type_ = id.type;
if (type_.kind !== 'Object') {
return false;
}
return (
isUseStateType(id) || // setState
type_.shapeId === 'BuiltInUseActionState' || // setActionState
isUseReducerType(id) || // dispatcher
type_.shapeId === 'BuiltInUseTransition' // startTransition
);
}
export function evaluatesToStableTypeOrContainer(
env: Environment,
{value}: Instruction,
): boolean {
if (value.kind === 'CallExpression' || value.kind === 'MethodCall') {
const callee =
value.kind === 'CallExpression' ? value.callee : value.property;
const calleeHookKind = getHookKind(env, callee.identifier);
switch (calleeHookKind) {
case 'useState':
case 'useReducer':
case 'useActionState':
case 'useRef':
case 'useTransition':
return true;
}
}
return false;
}
export function isUseEffectHookType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectHook'

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {
HIRFunction,
InstructionId,

View File

@@ -223,7 +223,6 @@ export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
export const BuiltInUseTransitionId = 'BuiltInUseTransition';
export const BuiltInStartTransitionId = 'BuiltInStartTransition';
export const BuiltInFireId = 'BuiltInFire';
export const BuiltInFireFunctionId = 'BuiltInFireFunction';
// ShapeRegistry with default definitions for built-ins.
export const BUILTIN_SHAPES: ShapeRegistry = new Map();

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {
ScopeId,
HIRFunction,
@@ -30,7 +23,9 @@ import {
FunctionExpression,
ObjectMethod,
PropertyLiteral,
convertHoistedLValueKind,
} from './HIR';
import {
collectHoistablePropertyLoads,
keyByScopeId,
@@ -116,7 +111,7 @@ export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
}
}
export function findTemporariesUsedOutsideDeclaringScope(
function findTemporariesUsedOutsideDeclaringScope(
fn: HIRFunction,
): ReadonlySet<DeclarationId> {
/*
@@ -378,7 +373,7 @@ type Decl = {
scope: Stack<ReactiveScope>;
};
export class DependencyCollectionContext {
class Context {
#declarations: Map<DeclarationId, Decl> = new Map();
#reassignments: Map<Identifier, Decl> = new Map();
@@ -471,6 +466,9 @@ export class DependencyCollectionContext {
}
this.#reassignments.set(identifier, decl);
}
hasDeclared(identifier: Identifier): boolean {
return this.#declarations.has(identifier.declarationId);
}
// Checks if identifier is a valid dependency in the current scope
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
@@ -645,10 +643,7 @@ enum HIRValue {
Terminal,
}
export function handleInstruction(
instr: Instruction,
context: DependencyCollectionContext,
): void {
function handleInstruction(instr: Instruction, context: Context): void {
const {id, value, lvalue} = instr;
context.declare(lvalue.identifier, {
id,
@@ -672,21 +667,21 @@ export function handleInstruction(
});
} else if (value.kind === 'DeclareLocal' || value.kind === 'DeclareContext') {
/*
* Some variables may be declared and never initialized. We need
* to retain (and hoist) these declarations if they are included
* in a reactive scope. One approach is to simply add all `DeclareLocal`s
* as scope declarations.
* Some variables may be declared and never initialized. We need to retain
* (and hoist) these declarations if they are included in a reactive scope.
* One approach is to simply add all `DeclareLocal`s as scope declarations.
*
* Context variables with hoisted declarations only become live after their
* first assignment. We only declare real DeclareLocal / DeclareContext
* instructions (not hoisted ones) to avoid generating dependencies on
* hoisted declarations.
*/
/*
* We add context variable declarations here, not at `StoreContext`, since
* context Store / Loads are modeled as reads and mutates to the underlying
* variable reference (instead of through intermediate / inlined temporaries)
*/
context.declare(value.lvalue.place.identifier, {
id,
scope: context.currentScope,
});
if (convertHoistedLValueKind(value.lvalue.kind) === null) {
context.declare(value.lvalue.place.identifier, {
id,
scope: context.currentScope,
});
}
} else if (value.kind === 'Destructure') {
context.visitOperand(value.value);
for (const place of eachPatternOperand(value.lvalue.pattern)) {
@@ -698,6 +693,23 @@ export function handleInstruction(
scope: context.currentScope,
});
}
} else if (value.kind === 'StoreContext') {
/**
* Some StoreContext variables have hoisted declarations. If we're storing
* to a context variable that hasn't yet been declared, the StoreContext is
* the declaration.
* (see corresponding logic in PruneHoistedContext)
*/
if (!context.hasDeclared(value.lvalue.place.identifier)) {
context.declare(value.lvalue.place.identifier, {
id,
scope: context.currentScope,
});
}
for (const operand of eachInstructionValueOperand(value)) {
context.visitOperand(operand);
}
} else {
for (const operand of eachInstructionValueOperand(value)) {
context.visitOperand(operand);
@@ -711,7 +723,7 @@ function collectDependencies(
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
const context = new DependencyCollectionContext(
const context = new Context(
usedOutsideDeclaringScope,
temporaries,
processedInstrsInOptional,

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {CompilerError} from '..';
import {BlockId, GotoVariant, HIRFunction} from './HIR';

View File

@@ -32,5 +32,5 @@ export {
} from './HIRBuilder';
export {mergeConsecutiveBlocks} from './MergeConsecutiveBlocks';
export {mergeOverlappingReactiveScopesHIR} from './MergeOverlappingReactiveScopesHIR';
export {printFunction, printHIR, printFunctionWithOutlined} from './PrintHIR';
export {printFunction, printHIR} from './PrintHIR';
export {pruneUnusedLabelsHIR} from './PruneUnusedLabelsHIR';

View File

@@ -1,11 +1,3 @@
/**
* 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.
*/
import * as t from '@babel/types';
import {CompilerError, SourceLocation} from '..';
import {
ArrayExpression,
@@ -22,30 +14,17 @@ import {
ScopeId,
ReactiveScopeDependency,
Place,
ReactiveScope,
ReactiveScopeDependencies,
Terminal,
isUseRefType,
isSetStateType,
isFireFunctionType,
makeScopeId,
} from '../HIR';
import {collectHoistablePropertyLoadsInInnerFn} from '../HIR/CollectHoistablePropertyLoads';
import {collectOptionalChainSidemap} from '../HIR/CollectOptionalChainDependencies';
import {ReactiveScopeDependencyTreeHIR} from '../HIR/DeriveMinimalDependenciesHIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {
createTemporaryPlace,
fixScopeAndIdentifierRanges,
markInstructionIds,
} from '../HIR/HIRBuilder';
import {
collectTemporariesSidemap,
DependencyCollectionContext,
handleInstruction,
} from '../HIR/PropagateScopeDependenciesHIR';
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
import {empty} from '../Utils/Stack';
import {getOrInsertWith} from '../Utils/utils';
/**
@@ -74,7 +53,10 @@ export function inferEffectDependencies(fn: HIRFunction): void {
const autodepFnLoads = new Map<IdentifierId, number>();
const autodepModuleLoads = new Map<IdentifierId, Map<string, number>>();
const scopeInfos = new Map<ScopeId, ReactiveScopeDependencies>();
const scopeInfos = new Map<
ScopeId,
{pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean}
>();
const loadGlobals = new Set<IdentifierId>();
@@ -88,18 +70,19 @@ export function inferEffectDependencies(fn: HIRFunction): void {
const reactiveIds = inferReactiveIdentifiers(fn);
for (const [, block] of fn.body.blocks) {
if (block.terminal.kind === 'scope') {
if (
block.terminal.kind === 'scope' ||
block.terminal.kind === 'pruned-scope'
) {
const scopeBlock = fn.body.blocks.get(block.terminal.block)!;
if (
scopeBlock.instructions.length === 1 &&
scopeBlock.terminal.kind === 'goto' &&
scopeBlock.terminal.block === block.terminal.fallthrough
) {
scopeInfos.set(
block.terminal.scope.id,
block.terminal.scope.dependencies,
);
}
scopeInfos.set(block.terminal.scope.id, {
pruned: block.terminal.kind === 'pruned-scope',
deps: block.terminal.scope.dependencies,
hasSingleInstr:
scopeBlock.instructions.length === 1 &&
scopeBlock.terminal.kind === 'goto' &&
scopeBlock.terminal.block === block.terminal.fallthrough,
});
}
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
for (const instr of block.instructions) {
@@ -181,12 +164,22 @@ export function inferEffectDependencies(fn: HIRFunction): void {
fnExpr.lvalue.identifier.scope != null
? scopeInfos.get(fnExpr.lvalue.identifier.scope.id)
: null;
let minimalDeps: Set<ReactiveScopeDependency>;
if (scopeInfo != null) {
minimalDeps = new Set(scopeInfo);
} else {
minimalDeps = inferMinimalDependencies(fnExpr);
CompilerError.invariant(scopeInfo != null, {
reason: 'Expected function expression scope to exist',
loc: value.loc,
});
if (scopeInfo.pruned || !scopeInfo.hasSingleInstr) {
/**
* TODO: retry pipeline that ensures effect function expressions
* are placed into their own scope
*/
CompilerError.throwTodo({
reason:
'[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction',
loc: fnExpr.loc,
});
}
/**
* Step 1: push dependencies to the effect deps array
*
@@ -194,14 +187,11 @@ export function inferEffectDependencies(fn: HIRFunction): void {
* the `infer-effect-deps/pruned-nonreactive-obj` fixture for an
* explanation.
*/
const usedDeps = [];
for (const dep of minimalDeps) {
for (const dep of scopeInfo.deps) {
if (
((isUseRefType(dep.identifier) ||
(isUseRefType(dep.identifier) ||
isSetStateType(dep.identifier)) &&
!reactiveIds.has(dep.identifier.id)) ||
isFireFunctionType(dep.identifier)
!reactiveIds.has(dep.identifier.id)
) {
// exclude non-reactive hook results, which will never be in a memo block
continue;
@@ -215,23 +205,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
);
newInstructions.push(...instructions);
effectDeps.push(place);
usedDeps.push(dep);
}
// For LSP autodeps feature.
const decorations: Array<t.SourceLocation> = [];
for (const loc of collectDepUsages(usedDeps, fnExpr.value)) {
if (typeof loc === 'symbol') {
continue;
}
decorations.push(loc);
}
if (typeof value.loc !== 'symbol') {
fn.env.logger?.logEvent(fn.env.filename, {
kind: 'AutoDepsDecorations',
fnLoc: value.loc,
decorations,
});
}
newInstructions.push({
@@ -244,7 +217,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
// Step 2: push the inferred deps array as an argument of the useEffect
value.args.push({...depsPlace, effect: Effect.Freeze});
rewriteInstrs.set(instr.id, newInstructions);
fn.env.inferredEffectLocations.add(callee.loc);
} else if (loadGlobals.has(value.args[0].identifier.id)) {
// Global functions have no reactive dependencies, so we can insert an empty array
newInstructions.push({
@@ -255,32 +227,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
});
value.args.push({...depsPlace, effect: Effect.Freeze});
rewriteInstrs.set(instr.id, newInstructions);
fn.env.inferredEffectLocations.add(callee.loc);
}
} else if (
value.args.length >= 2 &&
value.args.length - 1 === autodepFnLoads.get(callee.identifier.id) &&
value.args[0] != null &&
value.args[0].kind === 'Identifier'
) {
const penultimateArg = value.args[value.args.length - 2];
const depArrayArg = value.args[value.args.length - 1];
if (
depArrayArg.kind !== 'Spread' &&
penultimateArg.kind !== 'Spread' &&
typeof depArrayArg.loc !== 'symbol' &&
typeof penultimateArg.loc !== 'symbol' &&
typeof value.loc !== 'symbol'
) {
fn.env.logger?.logEvent(fn.env.filename, {
kind: 'AutoDepsEligible',
fnLoc: value.loc,
depArrayLoc: {
...depArrayArg.loc,
start: penultimateArg.loc.end,
end: depArrayArg.loc.end,
},
});
}
}
}
@@ -390,163 +336,3 @@ function inferReactiveIdentifiers(fn: HIRFunction): Set<IdentifierId> {
}
return reactiveIds;
}
function collectDepUsages(
deps: Array<ReactiveScopeDependency>,
fnExpr: FunctionExpression,
): Array<SourceLocation> {
const identifiers: Map<IdentifierId, ReactiveScopeDependency> = new Map();
const loadedDeps: Set<IdentifierId> = new Set();
const sourceLocations = [];
for (const dep of deps) {
identifiers.set(dep.identifier.id, dep);
}
for (const [, block] of fnExpr.loweredFunc.func.body.blocks) {
for (const instr of block.instructions) {
if (
instr.value.kind === 'LoadLocal' &&
identifiers.has(instr.value.place.identifier.id)
) {
loadedDeps.add(instr.lvalue.identifier.id);
}
for (const place of eachInstructionOperand(instr)) {
if (loadedDeps.has(place.identifier.id)) {
// TODO(@jbrown215): handle member exprs!!
sourceLocations.push(place.identifier.loc);
}
}
}
}
return sourceLocations;
}
function inferMinimalDependencies(
fnInstr: TInstruction<FunctionExpression>,
): Set<ReactiveScopeDependency> {
const fn = fnInstr.value.loweredFunc.func;
const temporaries = collectTemporariesSidemap(fn, new Set());
const {
hoistableObjects,
processedInstrsInOptional,
temporariesReadInOptional,
} = collectOptionalChainSidemap(fn);
const hoistablePropertyLoads = collectHoistablePropertyLoadsInInnerFn(
fnInstr,
temporaries,
hoistableObjects,
);
const hoistableToFnEntry = hoistablePropertyLoads.get(fn.body.entry);
CompilerError.invariant(hoistableToFnEntry != null, {
reason:
'[InferEffectDependencies] Internal invariant broken: missing entry block',
loc: fnInstr.loc,
});
const dependencies = inferDependencies(
fnInstr,
new Map([...temporaries, ...temporariesReadInOptional]),
processedInstrsInOptional,
);
const tree = new ReactiveScopeDependencyTreeHIR(
[...hoistableToFnEntry.assumedNonNullObjects].map(o => o.fullPath),
);
for (const dep of dependencies) {
tree.addDependency({...dep});
}
return tree.deriveMinimalDependencies();
}
function inferDependencies(
fnInstr: TInstruction<FunctionExpression>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
processedInstrsInOptional: ReadonlySet<Instruction | Terminal>,
): Set<ReactiveScopeDependency> {
const fn = fnInstr.value.loweredFunc.func;
const context = new DependencyCollectionContext(
new Set(),
temporaries,
processedInstrsInOptional,
);
for (const dep of fn.context) {
context.declare(dep.identifier, {
id: makeInstructionId(0),
scope: empty(),
});
}
const placeholderScope: ReactiveScope = {
id: makeScopeId(0),
range: {
start: fnInstr.id,
end: makeInstructionId(fnInstr.id + 1),
},
dependencies: new Set(),
reassignments: new Set(),
declarations: new Map(),
earlyReturnValue: null,
merged: new Set(),
loc: GeneratedSource,
};
context.enterScope(placeholderScope);
inferDependenciesInFn(fn, context, temporaries);
context.exitScope(placeholderScope, false);
const resultUnfiltered = context.deps.get(placeholderScope);
CompilerError.invariant(resultUnfiltered != null, {
reason:
'[InferEffectDependencies] Internal invariant broken: missing scope dependencies',
loc: fn.loc,
});
const fnContext = new Set(fn.context.map(dep => dep.identifier.id));
const result = new Set<ReactiveScopeDependency>();
for (const dep of resultUnfiltered) {
if (fnContext.has(dep.identifier.id)) {
result.add(dep);
}
}
return result;
}
function inferDependenciesInFn(
fn: HIRFunction,
context: DependencyCollectionContext,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
): void {
for (const [, block] of fn.body.blocks) {
// Record referenced optional chains in phis
for (const phi of block.phis) {
for (const operand of phi.operands) {
const maybeOptionalChain = temporaries.get(operand[1].identifier.id);
if (maybeOptionalChain) {
context.visitDependency(maybeOptionalChain);
}
}
}
for (const instr of block.instructions) {
if (
instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod'
) {
context.declare(instr.lvalue.identifier, {
id: instr.id,
scope: context.currentScope,
});
/**
* Recursively visit the inner function to extract dependencies
*/
const innerFn = instr.value.loweredFunc.func;
context.enterInnerFn(instr as TInstruction<FunctionExpression>, () => {
inferDependenciesInFn(innerFn, context, temporaries);
});
} else {
handleInstruction(instr, context);
}
}
}
}

View File

@@ -9,19 +9,14 @@ import {CompilerError} from '..';
import {
BlockId,
Effect,
Environment,
HIRFunction,
Identifier,
IdentifierId,
Instruction,
Place,
computePostDominatorTree,
evaluatesToStableTypeOrContainer,
getHookKind,
isStableType,
isStableTypeContainer,
isUseOperator,
isUseRefType,
} from '../HIR';
import {PostDominator} from '../HIR/Dominator';
import {
@@ -36,103 +31,6 @@ import {
import DisjointSet from '../Utils/DisjointSet';
import {assertExhaustive} from '../Utils/utils';
/**
* Side map to track and propagate sources of stability (i.e. hook calls such as
* `useRef()` and property reads such as `useState()[1]). Note that this
* requires forward data flow analysis since stability is not part of React
* Compiler's type system.
*/
class StableSidemap {
map: Map<IdentifierId, {isStable: boolean}> = new Map();
env: Environment;
constructor(env: Environment) {
this.env = env;
}
handleInstruction(instr: Instruction): void {
const {value, lvalue} = instr;
switch (value.kind) {
case 'CallExpression':
case 'MethodCall': {
/**
* Sources of stability are known hook calls
*/
if (evaluatesToStableTypeOrContainer(this.env, instr)) {
if (isStableType(lvalue.identifier)) {
this.map.set(lvalue.identifier.id, {
isStable: true,
});
} else {
this.map.set(lvalue.identifier.id, {
isStable: false,
});
}
} else if (
this.env.config.enableTreatRefLikeIdentifiersAsRefs &&
isUseRefType(lvalue.identifier)
) {
this.map.set(lvalue.identifier.id, {
isStable: true,
});
}
break;
}
case 'Destructure':
case 'PropertyLoad': {
/**
* PropertyLoads may from stable containers may also produce stable
* values. ComputedLoads are technically safe for now (as all stable
* containers have differently-typed elements), but are not handled as
* they should be rare anyways.
*/
const source =
value.kind === 'Destructure'
? value.value.identifier.id
: value.object.identifier.id;
const entry = this.map.get(source);
if (entry) {
for (const lvalue of eachInstructionLValue(instr)) {
if (isStableTypeContainer(lvalue.identifier)) {
this.map.set(lvalue.identifier.id, {
isStable: false,
});
} else if (isStableType(lvalue.identifier)) {
this.map.set(lvalue.identifier.id, {
isStable: true,
});
}
}
}
break;
}
case 'StoreLocal': {
const entry = this.map.get(value.value.identifier.id);
if (entry) {
this.map.set(lvalue.identifier.id, entry);
this.map.set(value.lvalue.place.identifier.id, entry);
}
break;
}
case 'LoadLocal': {
const entry = this.map.get(value.place.identifier.id);
if (entry) {
this.map.set(lvalue.identifier.id, entry);
}
break;
}
}
}
isStable(id: IdentifierId): boolean {
const entry = this.map.get(id);
return entry != null ? entry.isStable : false;
}
}
/*
* Infers which `Place`s are reactive, ie may *semantically* change
* over the course of the component/hook's lifetime. Places are reactive
@@ -213,7 +111,6 @@ class StableSidemap {
*/
export function inferReactivePlaces(fn: HIRFunction): void {
const reactiveIdentifiers = new ReactivityMap(findDisjointMutableValues(fn));
const stableIdentifierSources = new StableSidemap(fn.env);
for (const param of fn.params) {
const place = param.kind === 'Identifier' ? param : param.place;
reactiveIdentifiers.markReactive(place);
@@ -287,7 +184,6 @@ export function inferReactivePlaces(fn: HIRFunction): void {
}
}
for (const instruction of block.instructions) {
stableIdentifierSources.handleInstruction(instruction);
const {value} = instruction;
let hasReactiveInput = false;
/*
@@ -322,13 +218,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
if (hasReactiveInput) {
for (const lvalue of eachInstructionLValue(instruction)) {
/**
* Note that it's not correct to mark all stable-typed identifiers
* as non-reactive, since ternaries and other value blocks can
* produce reactive identifiers typed as these.
* (e.g. `props.cond ? setState1 : setState2`)
*/
if (stableIdentifierSources.isStable(lvalue.identifier.id)) {
if (isStableType(lvalue.identifier)) {
continue;
}
reactiveIdentifiers.markReactive(lvalue);

View File

@@ -48,7 +48,7 @@ import {printIdentifier, printPlace} from '../HIR/PrintHIR';
import {eachPatternOperand} from '../HIR/visitors';
import {Err, Ok, Result} from '../Utils/Result';
import {GuardKind} from '../Utils/RuntimeDiagnosticConstants';
import {assertExhaustive, hasOwnProperty} from '../Utils/utils';
import {assertExhaustive} from '../Utils/utils';
import {buildReactiveFunction} from './BuildReactiveFunction';
import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
@@ -104,7 +104,6 @@ export type CodegenFunction = {
* This is true if the compiler has compiled inferred effect dependencies
*/
hasInferredEffect: boolean;
inferredEffectLocations: Set<SourceLocation>;
/**
* This is true if the compiler has compiled a fire to a useFire call
@@ -374,8 +373,6 @@ function codegenReactiveFunction(
const countMemoBlockVisitor = new CountMemoBlockVisitor(fn.env);
visitReactiveFunction(fn, countMemoBlockVisitor, undefined);
setMissingLocationsToNull(body);
return Ok({
type: 'CodegenFunction',
loc: fn.loc,
@@ -392,7 +389,6 @@ function codegenReactiveFunction(
outlined: [],
hasFireRewrite: fn.env.hasFireRewrite,
hasInferredEffect: fn.env.hasInferredEffect,
inferredEffectLocations: fn.env.inferredEffectLocations,
});
}
@@ -2117,17 +2113,10 @@ function codegenInstructionValue(
}
case 'TypeCastExpression': {
if (t.isTSType(instrValue.typeAnnotation)) {
if (instrValue.typeAnnotationKind === 'satisfies') {
value = t.tsSatisfiesExpression(
codegenPlaceToExpression(cx, instrValue.value),
instrValue.typeAnnotation,
);
} else {
value = t.tsAsExpression(
codegenPlaceToExpression(cx, instrValue.value),
instrValue.typeAnnotation,
);
}
value = t.tsAsExpression(
codegenPlaceToExpression(cx, instrValue.value),
instrValue.typeAnnotation,
);
} else {
value = t.typeCastExpression(
codegenPlaceToExpression(cx, instrValue.value),
@@ -2667,38 +2656,3 @@ function compareScopeDeclaration(
else if (aName > bName) return 1;
else return 0;
}
function setMissingLocationsToNull(ast: any): void {
if (Array.isArray(ast)) {
ast.forEach(item => setMissingLocationsToNull(item));
return;
} else if (
ast == null ||
typeof ast !== 'object' ||
typeof ast['type'] !== 'string'
) {
return;
}
if (ast['loc'] == null) {
ast['loc'] = {
start: {line: null, column: null, index: null},
end: {line: null, column: null, index: null},
filename: null,
identifierName: null,
};
}
for (const key in ast) {
if (!hasOwnProperty(ast, key)) {
continue;
}
const value = ast[key];
if (typeof value !== 'object') {
/*
* We handle this above too, but avoid extra function calls in the majority of
* cases where we're traversing an AST node's properties
*/
continue;
}
setMissingLocationsToNull(ast[key]);
}
}

View File

@@ -7,6 +7,7 @@
import {CompilerError} from '..';
import {
convertHoistedLValueKind,
DeclarationId,
InstructionKind,
ReactiveFunction,
@@ -50,37 +51,26 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
/**
* Remove hoisted declarations to preserve TDZ
*/
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedConst'
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Const,
if (instruction.value.kind === 'DeclareContext') {
const maybeNonHoisted = convertHoistedLValueKind(
instruction.value.lvalue.kind,
);
return {kind: 'remove'};
}
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedLet'
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Let,
);
return {kind: 'remove'};
}
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedFunction'
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
InstructionKind.Function,
);
return {kind: 'remove'};
if (maybeNonHoisted != null) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
maybeNonHoisted,
);
return {kind: 'remove'};
}
if (instruction.value.lvalue.kind === 'Let') {
/**
* We don't expect const context variables to be hoisted
*/
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_LET,
);
}
}
if (instruction.value.kind === 'StoreContext') {

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {
BlockId,
ReactiveFunction,

View File

@@ -14,10 +14,7 @@ export {extractScopeDeclarationsFromDestructuring} from './ExtractScopeDeclarati
export {inferReactiveScopeVariables} from './InferReactiveScopeVariables';
export {memoizeFbtAndMacroOperandsInSameScope} from './MemoizeFbtAndMacroOperandsInSameScope';
export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesThatInvalidateTogether';
export {
printReactiveFunction,
printReactiveFunctionWithOutlined,
} from './PrintReactiveFunction';
export {printReactiveFunction} from './PrintReactiveFunction';
export {promoteUsedTemporaries} from './PromoteUsedTemporaries';
export {propagateEarlyReturns} from './PropagateEarlyReturns';
export {pruneAllReactiveScopes} from './PruneAllReactiveScopes';

View File

@@ -34,11 +34,7 @@ import {
} from '../HIR';
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
import {getOrInsertWith} from '../Utils/utils';
import {
BuiltInFireFunctionId,
BuiltInFireId,
DefaultNonmutatingHook,
} from '../HIR/ObjectShape';
import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
import {eachInstructionOperand} from '../HIR/visitors';
import {printSourceLocationLine} from '../HIR/PrintHIR';
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
@@ -637,13 +633,6 @@ class Context {
() => createTemporaryPlace(this.#env, GeneratedSource),
);
fireFunctionBinding.identifier.type = {
kind: 'Function',
shapeId: BuiltInFireFunctionId,
return: {kind: 'Poly'},
isConstructor: false,
};
this.#capturedCalleeIdentifierIds.set(callee.identifier.id, {
fireFunctionBinding,
capturedCalleeIdentifier: callee.identifier,

View File

@@ -4,5 +4,4 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export {transformFire} from './TransformFire';

View File

@@ -4,7 +4,6 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
import {HIRFunction, IdentifierId} from '../HIR';
import {DEFAULT_GLOBALS} from '../HIR/Globals';

View File

@@ -0,0 +1,82 @@
## Input
```javascript
import {CONST_TRUE, useIdentity} from 'shared-runtime';
const hidden = CONST_TRUE;
function useFoo() {
const makeCb = useIdentity(() => {
const logIntervalId = () => {
log(intervalId);
};
let intervalId;
if (!hidden) {
intervalId = 2;
}
return () => {
logIntervalId();
};
});
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { CONST_TRUE, useIdentity } from "shared-runtime";
const hidden = CONST_TRUE;
function useFoo() {
const $ = _c(4);
const makeCb = useIdentity(_temp);
let t0;
if ($[0] !== makeCb) {
t0 = makeCb();
$[0] = makeCb;
$[1] = t0;
} else {
t0 = $[1];
}
let t1;
if ($[2] !== t0) {
t1 = <Stringify fn={t0} shouldInvokeFns={true} />;
$[2] = t0;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
}
function _temp() {
const logIntervalId = () => {
log(intervalId);
};
let intervalId;
if (!hidden) {
intervalId = 2;
}
return () => {
logIntervalId();
};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: exception) Stringify is not defined

View File

@@ -0,0 +1,25 @@
import {CONST_TRUE, useIdentity} from 'shared-runtime';
const hidden = CONST_TRUE;
function useFoo() {
const makeCb = useIdentity(() => {
const logIntervalId = () => {
log(intervalId);
};
let intervalId;
if (!hidden) {
intervalId = 2;
}
return () => {
logIntervalId();
};
});
return <Stringify fn={makeCb()} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};

View File

@@ -0,0 +1,63 @@
## Input
```javascript
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
const getX = () => x;
let x;
if (cond) {
x = CONST_NUMBER1;
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { CONST_NUMBER1, Stringify } from "shared-runtime";
function useHook(t0) {
const $ = _c(2);
const { cond } = t0;
let t1;
if ($[0] !== cond) {
const getX = () => x;
let x;
if (cond) {
x = CONST_NUMBER1;
}
t1 = <Stringify getX={getX} shouldInvokeFns={true} />;
$[0] = cond;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ cond: true }],
sequentialRenders: [{ cond: true }, { cond: true }, { cond: false }],
};
```
### Eval output
(kind: ok) <div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function","result":1},"shouldInvokeFns":true}</div>
<div>{"getX":{"kind":"Function"},"shouldInvokeFns":true}</div>

View File

@@ -0,0 +1,17 @@
import {CONST_NUMBER1, Stringify} from 'shared-runtime';
function useHook({cond}) {
const getX = () => x;
let x;
if (cond) {
x = CONST_NUMBER1;
}
return <Stringify getX={getX} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{cond: true}],
sequentialRenders: [{cond: true}, {cond: true}, {cond: false}],
};

View File

@@ -1,39 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0].value));
arr.push({value: foo});
return arr;
}
```
## Code
```javascript
// @inferEffectDependencies @panicThreshold(none)
import { useEffect } from "react";
import { print } from "shared-runtime";
function Component(t0) {
const { foo } = t0;
const arr = [];
useEffect(() => print(arr[0].value), [arr[0].value]);
arr.push({ value: foo });
return arr;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,12 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
import {print} from 'shared-runtime';
function Component({foo}) {
const arr = [];
// Taking either arr[0].value or arr as a dependency is reasonable
// as long as developers know what to expect.
useEffect(() => print(arr[0].value));
arr.push({value: foo});
return arr;
}

View File

@@ -1,38 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
function Component({arrRef}) {
// Avoid taking arr.current as a dependency
useEffect(() => print(arrRef.current));
arrRef.current.val = 2;
return arrRef;
}
```
## Code
```javascript
// @inferEffectDependencies @panicThreshold(none)
import { useEffect, useRef } from "react";
import { print } from "shared-runtime";
function Component(t0) {
const { arrRef } = t0;
useEffect(() => print(arrRef.current), [arrRef]);
arrRef.current.val = 2;
return arrRef;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,11 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useEffect, useRef} from 'react';
import {print} from 'shared-runtime';
function Component({arrRef}) {
// Avoid taking arr.current as a dependency
useEffect(() => print(arrRef.current));
arrRef.current.val = 2;
return arrRef;
}

View File

@@ -1,34 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
function Component({foo}) {
const arr = [];
useEffect(() => arr.push(foo));
arr.push(2);
return arr;
}
```
## Code
```javascript
// @inferEffectDependencies @panicThreshold(none)
import { useEffect } from "react";
function Component(t0) {
const { foo } = t0;
const arr = [];
useEffect(() => arr.push(foo), [arr, foo]);
arr.push(2);
return arr;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,9 +0,0 @@
// @inferEffectDependencies @panicThreshold(none)
import {useEffect} from 'react';
function Component({foo}) {
const arr = [];
useEffect(() => arr.push(foo));
arr.push(2);
return arr;
}

View File

@@ -1,31 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @noEmit
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
function ReactiveVariable({propVal}) {
const arr = [propVal];
useEffectWrapper(() => print(arr));
}
```
## Code
```javascript
// @inferEffectDependencies @noEmit
import { print } from "shared-runtime";
import useEffectWrapper from "useEffectWrapper";
function ReactiveVariable({ propVal }) {
const arr = [propVal];
useEffectWrapper(() => print(arr));
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,8 +0,0 @@
// @inferEffectDependencies @noEmit
import {print} from 'shared-runtime';
import useEffectWrapper from 'useEffectWrapper';
function ReactiveVariable({propVal}) {
const arr = [propVal];
useEffectWrapper(() => print(arr));
}

View File

@@ -1,69 +0,0 @@
## Input
```javascript
// @inferEffectDependencies
import {useRef, useEffect} from 'react';
import {print, mutate} from 'shared-runtime';
function Component({cond}) {
const arr = useRef([]);
const other = useRef([]);
// Although arr and other are both stable, derived is not
const derived = cond ? arr : other;
useEffect(() => {
mutate(derived.current);
print(derived.current);
});
return arr;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies
import { useRef, useEffect } from "react";
import { print, mutate } from "shared-runtime";
function Component(t0) {
const $ = _c(4);
const { cond } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = [];
$[0] = t1;
} else {
t1 = $[0];
}
const arr = useRef(t1);
let t2;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [];
$[1] = t2;
} else {
t2 = $[1];
}
const other = useRef(t2);
const derived = cond ? arr : other;
let t3;
if ($[2] !== derived) {
t3 = () => {
mutate(derived.current);
print(derived.current);
};
$[2] = derived;
$[3] = t3;
} else {
t3 = $[3];
}
useEffect(t3, [derived]);
return arr;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,15 +0,0 @@
// @inferEffectDependencies
import {useRef, useEffect} from 'react';
import {print, mutate} from 'shared-runtime';
function Component({cond}) {
const arr = useRef([]);
const other = useRef([]);
// Although arr and other are both stable, derived is not
const derived = cond ? arr : other;
useEffect(() => {
mutate(derived.current);
print(derived.current);
});
return arr;
}

View File

@@ -83,10 +83,10 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c2 } from "react/compiler-runtime"; // @inlineJsxTransform
function Parent(t0) {
const $ = _c2(3);
const $ = _c2(2);
const { children, ref } = t0;
let t1;
if ($[0] !== children || $[1] !== ref) {
if ($[0] !== children) {
if (DEV) {
t1 = <div ref={ref}>{children}</div>;
} else {
@@ -99,10 +99,9 @@ function Parent(t0) {
};
}
$[0] = children;
$[1] = ref;
$[2] = t1;
$[1] = t1;
} else {
t1 = $[2];
t1 = $[1];
}
return t1;
}

View File

@@ -1,55 +0,0 @@
## Input
```javascript
// @sourceMaps
export const Button = () => {
return <button>Click me</button>;
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @sourceMaps
export const Button = () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <button>Click me</button>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
};
```
## Source Map
```
{
"version": 3,
"names": [
"Button",
"$",
"_c",
"t0",
"Symbol",
"for"
],
"sources": [
"sourcemaps-simple.ts"
],
"sourcesContent": [
"// @sourceMaps\nexport const Button = () => {\n return <button>Click me</button>;\n};\n"
],
"mappings": "kDAAA;AACA,OAAO,MAAMA,MAAM,GAAGA,CAAA,YAAAC,CAAA,GAAAC,EAAA,QAAAC,EAAA,KAAAF,CAAA,QAAAG,MAAA,CAAAC,GAAA;IACbF,EAAA,UAAyB,CAAjB,QAAQ,EAAhB,MAAyB,EAAAF,CAAA,MAAAE,EAAA,SAAAA,EAAA,GAAAF,CAAA,YAAzBE,EAAyB,EACjC",
"ignoreList": []
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,4 +0,0 @@
// @sourceMaps
export const Button = () => {
return <button>Click me</button>;
};

View File

@@ -0,0 +1,42 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useRef} from 'react';
import {useSpecialEffect} from 'shared-runtime';
/**
* The retry pipeline disables memoization features, which means we need to
* provide an alternate implementation of effect dependencies which does not
* rely on memoization.
*/
function useFoo({cond}) {
const ref = useRef();
const derived = cond ? ref.current : makeObject();
useSpecialEffect(() => {
log(derived);
}, [derived]);
return ref;
}
```
## Error
```
11 | const ref = useRef();
12 | const derived = cond ? ref.current : makeObject();
> 13 | useSpecialEffect(() => {
| ^^^^^^^^^^^^^^^^^^^^^^^^
> 14 | log(derived);
| ^^^^^^^^^^^^^^^^^
> 15 | }, [derived]);
| ^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.. (Bailout reason: Invariant: Expected function expression scope to exist (13:15)) (13:15)
16 | return ref;
17 | }
18 |
```

View File

@@ -1,54 +0,0 @@
## Input
```javascript
// @inferEffectDependencies @panicThreshold(none)
import {useRef} from 'react';
import {useSpecialEffect} from 'shared-runtime';
/**
* The retry pipeline disables memoization features, which means we need to
* provide an alternate implementation of effect dependencies which does not
* rely on memoization.
*/
function useFoo({cond}) {
const ref = useRef();
const derived = cond ? ref.current : makeObject();
useSpecialEffect(() => {
log(derived);
}, [derived]);
return ref;
}
```
## Code
```javascript
// @inferEffectDependencies @panicThreshold(none)
import { useRef } from "react";
import { useSpecialEffect } from "shared-runtime";
/**
* The retry pipeline disables memoization features, which means we need to
* provide an alternate implementation of effect dependencies which does not
* rely on memoization.
*/
function useFoo(t0) {
const { cond } = t0;
const ref = useRef();
const derived = cond ? ref.current : makeObject();
useSpecialEffect(
() => {
log(derived);
},
[derived],
[derived],
);
return ref;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -49,7 +49,7 @@ function Component(props) {
} else {
t2 = $[4];
}
useEffect(t2, [props]);
useEffect(t2, [t1, props]);
return null;
}

View File

@@ -1,71 +0,0 @@
## Input
```javascript
// @enableUseTypeAnnotations
function Component(props: {id: number}) {
const x = makeArray(props.id) satisfies number[];
const y = x.at(0);
return y;
}
function makeArray<T>(x: T): Array<T> {
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{id: 42}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableUseTypeAnnotations
function Component(props) {
const $ = _c(4);
let t0;
if ($[0] !== props.id) {
t0 = makeArray(props.id);
$[0] = props.id;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0 satisfies number[];
let t1;
if ($[2] !== x) {
t1 = x.at(0);
$[2] = x;
$[3] = t1;
} else {
t1 = $[3];
}
const y = t1;
return y;
}
function makeArray(x) {
const $ = _c(2);
let t0;
if ($[0] !== x) {
t0 = [x];
$[0] = x;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ id: 42 }],
};
```
### Eval output
(kind: ok) 42

View File

@@ -1,15 +0,0 @@
// @enableUseTypeAnnotations
function Component(props: {id: number}) {
const x = makeArray(props.id) satisfies number[];
const y = x.at(0);
return y;
}
function makeArray<T>(x: T): Array<T> {
return [x];
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{id: 42}],
};

View File

@@ -1,41 +0,0 @@
## Input
```javascript
// @enableUseTypeAnnotations
import {identity} from 'shared-runtime';
function Component(props: {id: number}) {
const x = identity(props.id);
const y = x satisfies number;
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{id: 42}],
};
```
## Code
```javascript
// @enableUseTypeAnnotations
import { identity } from "shared-runtime";
function Component(props) {
const x = identity(props.id);
const y = x satisfies number;
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ id: 42 }],
};
```
### Eval output
(kind: ok) 42

View File

@@ -1,13 +0,0 @@
// @enableUseTypeAnnotations
import {identity} from 'shared-runtime';
function Component(props: {id: number}) {
const x = identity(props.id);
const y = x satisfies number;
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{id: 42}],
};

View File

@@ -32,17 +32,13 @@ export {
ValueKind,
parseConfigPragmaForTests,
printHIR,
printFunctionWithOutlined,
validateEnvironmentConfig,
type EnvironmentConfig,
type ExternalFunction,
type Hook,
type SourceLocation,
} from './HIR';
export {
printReactiveFunction,
printReactiveFunctionWithOutlined,
} from './ReactiveScopes';
export {printReactiveFunction} from './ReactiveScopes';
declare global {
let __DEV__: boolean | null | undefined;
}

View File

@@ -4,7 +4,7 @@
"module": "ES2015",
"moduleResolution": "Bundler",
"rootDir": "src",
"noEmit": true,
"outDir": "dist",
"jsx": "react-jsxdev",
// weaken strictness from preset
"importsNotUsedAsValues": "remove",

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {defineConfig} from 'tsup';
export default defineConfig({

View File

@@ -92,8 +92,36 @@ const tests: CompilerTestCases = {
}
`,
},
{
// Don't report the issue if Flow already has
name: '[InvalidInput] Ref access during render',
code: normalizeIndent`
function Component(props) {
const ref = useRef(null);
// $FlowFixMe[react-rule-unsafe-ref]
const value = ref.current;
return value;
}
`,
},
],
invalid: [
{
name: '[InvalidInput] Ref access during render',
code: normalizeIndent`
function Component(props) {
const ref = useRef(null);
const value = ref.current;
return value;
}
`,
errors: [
{
message:
'Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)',
},
],
},
{
name: 'Reportable levels can be configured',
options: [{reportableLevels: new Set([ErrorSeverity.Todo])}],

View File

@@ -105,9 +105,6 @@ const COMPILER_OPTIONS: Partial<PluginOptions> = {
panicThreshold: 'none',
// Don't emit errors on Flow suppressions--Flow already gave a signal
flowSuppressions: false,
environment: validateEnvironmentConfig({
validateRefAccessDuringRender: false,
}),
};
const rule: Rule.RuleModule = {
@@ -152,14 +149,10 @@ const rule: Rule.RuleModule = {
}
let shouldReportUnusedOptOutDirective = true;
const options: PluginOptions = parsePluginOptions({
const options: PluginOptions = {
...parsePluginOptions(userOpts),
...COMPILER_OPTIONS,
...userOpts,
environment: {
...COMPILER_OPTIONS.environment,
...userOpts.environment,
},
});
};
const userLogger: Logger | null = options.logger;
options.logger = {
logEvent: (filename, event): void => {

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {defineConfig} from 'tsup';
export default defineConfig({

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {defineConfig} from 'tsup';
export default defineConfig({

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
export const config = {
knownIncompatibleLibraries: [
'mobx-react',

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {defineConfig} from 'tsup';
export default defineConfig({

View File

@@ -1,10 +1,3 @@
/**
* 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.
*/
import {defineConfig} from 'tsup';
export default defineConfig({

View File

@@ -1,106 +0,0 @@
/**
* 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.
*/
import * as vscode from 'vscode';
import {
LanguageClient,
RequestType,
type Position,
} from 'vscode-languageclient/node';
import {positionLiteralToVSCodePosition, positionsToRange} from './mapping';
export type AutoDepsDecorationsLSPEvent = {
useEffectCallExpr: [Position, Position];
decorations: Array<[Position, Position]>;
};
export interface AutoDepsDecorationsParams {
position: Position;
}
export namespace AutoDepsDecorationsRequest {
export const type = new RequestType<
AutoDepsDecorationsParams,
AutoDepsDecorationsLSPEvent | null,
void
>('react/autodeps_decorations');
}
const inferredEffectDepDecoration =
vscode.window.createTextEditorDecorationType({
// TODO: make configurable?
borderColor: new vscode.ThemeColor('diffEditor.move.border'),
borderStyle: 'solid',
borderWidth: '0 0 4px 0',
});
let currentlyDecoratedAutoDepFnLoc: vscode.Range | null = null;
export function getCurrentlyDecoratedAutoDepFnLoc(): vscode.Range | null {
return currentlyDecoratedAutoDepFnLoc;
}
export function setCurrentlyDecoratedAutoDepFnLoc(range: vscode.Range): void {
currentlyDecoratedAutoDepFnLoc = range;
}
export function clearCurrentlyDecoratedAutoDepFnLoc(): void {
currentlyDecoratedAutoDepFnLoc = null;
}
let decorationRequestId = 0;
export type AutoDepsDecorationsOptions = {
shouldUpdateCurrent: boolean;
};
export function requestAutoDepsDecorations(
client: LanguageClient,
position: vscode.Position,
options: AutoDepsDecorationsOptions,
) {
const id = ++decorationRequestId;
client
.sendRequest(AutoDepsDecorationsRequest.type, {position})
.then(response => {
if (response !== null) {
const {
decorations,
useEffectCallExpr: [start, end],
} = response;
// Maintain ordering
if (decorationRequestId === id) {
if (options.shouldUpdateCurrent) {
setCurrentlyDecoratedAutoDepFnLoc(positionsToRange(start, end));
}
drawInferredEffectDepDecorations(decorations);
}
} else {
clearCurrentlyDecoratedAutoDepFnLoc();
clearDecorations(inferredEffectDepDecoration);
}
});
}
export function drawInferredEffectDepDecorations(
decorations: Array<[Position, Position]>,
): void {
const decorationOptions = decorations.map(([start, end]) => {
return {
range: new vscode.Range(
positionLiteralToVSCodePosition(start),
positionLiteralToVSCodePosition(end),
),
hoverMessage: 'Inferred as an effect dependency',
};
});
vscode.window.activeTextEditor?.setDecorations(
inferredEffectDepDecoration,
decorationOptions,
);
}
export function clearDecorations(
decorationType: vscode.TextEditorDecorationType,
) {
vscode.window.activeTextEditor?.setDecorations(decorationType, []);
}

View File

@@ -1,80 +0,0 @@
/**
* 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.
*/
type RGB = [number, number, number];
const int = Math.floor;
export class Color {
constructor(
private r: number,
private g: number,
private b: number,
) {}
toAlphaString(a: number) {
return this.toCssString(a);
}
toString() {
return this.toCssString(1);
}
/**
* Adjust the color by a multiplier to lighten (`> 1.0`) or darken (`< 1.0`) the color. Returns a new
* instance.
*/
adjusted(mult: number) {
const adjusted = Color.redistribute([
this.r * mult,
this.g * mult,
this.b * mult,
]);
return new Color(...adjusted);
}
private toCssString(a: number) {
return `rgba(${this.r},${this.g},${this.b},${a})`;
}
/**
* Redistributes rgb, maintaing hue until its clamped.
* https://stackoverflow.com/a/141943
*/
private static redistribute([r, g, b]: RGB): RGB {
const threshold = 255.999;
const max = Math.max(r, g, b);
if (max <= threshold) {
return [int(r), int(g), int(b)];
}
const total = r + g + b;
if (total >= 3 * threshold) {
return [int(threshold), int(threshold), int(threshold)];
}
const x = (3 * threshold - total) / (3 * max - total);
const gray = threshold - x * max;
return [int(gray + x * r), int(gray + x * g), int(gray + x * b)];
}
}
export const BLACK = new Color(0, 0, 0);
export const WHITE = new Color(255, 255, 255);
const COLOR_POOL = [
new Color(249, 65, 68),
new Color(243, 114, 44),
new Color(248, 150, 30),
new Color(249, 132, 74),
new Color(249, 199, 79),
new Color(144, 190, 109),
new Color(67, 170, 139),
new Color(77, 144, 142),
new Color(87, 117, 144),
new Color(39, 125, 161),
];
export function getColorFor(index: number): Color {
return COLOR_POOL[Math.abs(index) % COLOR_POOL.length]!;
}

View File

@@ -1,34 +1,17 @@
/**
* 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.
*/
import * as path from 'path';
import * as vscode from 'vscode';
import {ExtensionContext, window as Window} from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
type Position,
ServerOptions,
TransportKind,
} from 'vscode-languageclient/node';
import {positionLiteralToVSCodePosition} from './mapping';
import {
getCurrentlyDecoratedAutoDepFnLoc,
requestAutoDepsDecorations,
} from './autodeps';
let client: LanguageClient;
export function activate(context: vscode.ExtensionContext) {
export function activate(context: ExtensionContext) {
const serverModule = context.asAbsolutePath(path.join('dist', 'server.js'));
const documentSelector = [
{scheme: 'file', language: 'javascriptreact'},
{scheme: 'file', language: 'typescriptreact'},
];
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
@@ -44,7 +27,10 @@ export function activate(context: vscode.ExtensionContext) {
};
const clientOptions: LanguageClientOptions = {
documentSelector,
documentSelector: [
{scheme: 'file', language: 'javascriptreact'},
{scheme: 'file', language: 'typescriptreact'},
],
progressOnInitialization: true,
};
@@ -57,39 +43,12 @@ export function activate(context: vscode.ExtensionContext) {
clientOptions,
);
} catch {
vscode.window.showErrorMessage(
Window.showErrorMessage(
`React Analyzer couldn't be started. See the output channel for details.`,
);
return;
}
vscode.languages.registerHoverProvider(documentSelector, {
provideHover(_document, position, _token) {
requestAutoDepsDecorations(client, position, {shouldUpdateCurrent: true});
return null;
},
});
vscode.workspace.onDidChangeTextDocument(async _e => {
const currentlyDecoratedAutoDepFnLoc = getCurrentlyDecoratedAutoDepFnLoc();
if (currentlyDecoratedAutoDepFnLoc !== null) {
requestAutoDepsDecorations(client, currentlyDecoratedAutoDepFnLoc.start, {
shouldUpdateCurrent: false,
});
}
});
vscode.commands.registerCommand(
'react.requestAutoDepsDecorations',
(position: Position) => {
requestAutoDepsDecorations(
client,
positionLiteralToVSCodePosition(position),
{shouldUpdateCurrent: true},
);
},
);
client.registerProposedFeatures();
client.start();
}
@@ -98,5 +57,4 @@ export function deactivate(): Thenable<void> | undefined {
if (client !== undefined) {
return client.stop();
}
return;
}

Some files were not shown because too many files have changed in this diff Show More