Compare commits

..

1 Commits

Author SHA1 Message Date
Mofei Zhang
464a6c6efc [compiler] Repro for object spread and Array.from with mutable iterators
See newly added test fixtures. Repros fixed in later prs of this stack
2025-03-04 16:56:43 -05:00
599 changed files with 10329 additions and 34339 deletions

View File

@@ -7,14 +7,9 @@ on:
- compiler/**
- .github/workflows/compiler_**.yml
permissions: {}
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
@@ -24,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -8,8 +8,6 @@ on:
- compiler/**
- .github/workflows/compiler_playground.yml
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -38,27 +36,10 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-and-playground-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ 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"
- 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 chromium
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: npx playwright install-deps
if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
- run: CI=true yarn test
- run: ls -R test-results
if: '!cancelled()'
@@ -68,4 +49,3 @@ jobs:
with:
name: test-results
path: compiler/apps/playground/test-results
if-no-files-found: ignore

View File

@@ -20,12 +20,11 @@ on:
NPM_TOKEN:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
defaults:
@@ -47,11 +46,9 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ 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

View File

@@ -15,8 +15,6 @@ on:
required: true
type: string
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -5,8 +5,6 @@ on:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
- cron: 10 16 * * 1,2,3,4,5
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -5,8 +5,6 @@ on:
# At 10 minutes past 9:00 on Mon
- cron: 10 9 * * 1
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -8,8 +8,6 @@ on:
- compiler/**
- .github/workflows/compiler_typescript.yml
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -47,13 +45,10 @@ 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') }}
path: "**/node_modules"
key: compiler-node_modules-${{ 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
@@ -71,11 +66,9 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ 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:
@@ -97,12 +90,10 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ 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
if: matrix.workspace_name != 'react-forgive'
if: runner.os != 'Linux' && matrix.workspace_name != 'react-forgive'

View File

@@ -9,8 +9,6 @@ on:
required: false
type: string
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -20,9 +18,6 @@ jobs:
download_build:
name: Download base build
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
@@ -34,15 +29,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- name: Download react-devtools artifacts for base revision
run: |
git fetch origin main
@@ -54,7 +47,6 @@ jobs:
with:
name: build
path: build
if-no-files-found: error
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
@@ -71,13 +63,11 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -93,20 +83,17 @@ jobs:
with:
name: react-devtools
path: build/devtools.tgz
if-no-files-found: error
# Simplifies getting the extension for local testing
- name: Archive chrome extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-chrome-extension
path: build/devtools/chrome-extension.zip
if-no-files-found: error
- name: Archive firefox extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-firefox-extension
path: build/devtools/firefox-extension.zip
if-no-files-found: error
run_devtools_tests_for_versions:
name: Run DevTools tests for versions
@@ -133,11 +120,9 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ 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
@@ -169,28 +154,17 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ 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 }}
@@ -202,4 +176,3 @@ jobs:
with:
name: screenshots
path: ./tmp/screenshots
if-no-files-found: warn

View File

@@ -7,8 +7,6 @@ on:
paths-ignore:
- compiler/**
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -19,95 +17,6 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
# ----- NODE_MODULES CACHE -----
# Centralize the node_modules cache so it is saved once and each subsequent job only needs to
# restore the cache. Prevents race conditions where multiple workflows try to write to the cache.
runtime_node_modules_cache:
name: Cache Runtime node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Warm with old cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
runtime_compiler_node_modules_cache:
name: Cache Runtime, Compiler node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Warm with old cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# ----- FLOW -----
discover_flow_inline_configs:
name: Discover flow inline configs
@@ -127,7 +36,7 @@ jobs:
flow:
name: Flow check ${{ matrix.flow_inline_config_shortname }}
needs: [discover_flow_inline_configs, runtime_node_modules_cache]
needs: discover_flow_inline_configs
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -143,25 +52,19 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
# ----- FIZZ -----
check_generated_fizz_runtime:
name: Confirm generated inline Fizz runtime is up to date
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -173,19 +76,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: |
yarn generate-inline-fizz-runtime
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
@@ -193,7 +91,6 @@ jobs:
# ----- FEATURE FLAGS -----
flags:
name: Check flags
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -205,22 +102,19 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn flags
# ----- TESTS -----
test:
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -259,37 +153,27 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# yml is dumb. update the --total arg to yarn build if you change the number of workers
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
release_channel: [stable, experimental]
steps:
- uses: actions/checkout@v4
@@ -299,30 +183,21 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11.0.22
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn build --index=${{ matrix.worker_id }} --total=25 --r=${{ matrix.release_channel }} --ci
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
CI: github
RELEASE_CHANNEL: ${{ matrix.release_channel }}
@@ -336,11 +211,10 @@ jobs:
with:
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
path: build
if-no-files-found: error
test_build:
name: yarn test-build
needs: [build_and_lint, runtime_compiler_node_modules_cache]
needs: build_and_lint
strategy:
fail-fast: false
matrix:
@@ -375,16 +249,9 @@ jobs:
# TODO: Test more persistent configurations?
]
shard:
- 1/10
- 2/10
- 3/10
- 4/10
- 5/10
- 6/10
- 7/10
- 8/10
- 9/10
- 10/10
- 1/3
- 2/3
- 3/3
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -394,25 +261,16 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -425,11 +283,7 @@ 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
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -441,19 +295,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -462,7 +311,7 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -472,29 +321,16 @@ 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
path: |
./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
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -506,19 +342,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -534,7 +365,7 @@ jobs:
check_release_dependencies:
name: Check release dependencies
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -546,19 +377,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -583,16 +409,15 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/dom/yarn.lock') }}
path: "**/node_modules"
key: fixtures_dom-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn --cwd fixtures/dom install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn install --frozen-lockfile
working-directory: fixtures/dom
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -627,31 +452,14 @@ jobs:
# That means dependencies of the built packages are not installed.
# We need to install dependencies of the workroot to fulfill all dependency constraints
- name: Restore cached node_modules
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'fixtures/flight/yarn.lock') }}
path: "**/node_modules"
key: fixtures_flight-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd fixtures/flight 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"
- 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 }}
- name: Playwright install deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
working-directory: fixtures/flight
run: npx playwright install --with-deps chromium
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -660,6 +468,16 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- name: Install fixture dependencies
working-directory: fixtures/flight
run: |
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
if [ $? -ne 0 ]; then
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
fi
- name: Playwright install deps
working-directory: fixtures/flight
run: npx playwright install --with-deps chromium
- name: Run tests
working-directory: fixtures/flight
run: yarn test
@@ -671,18 +489,16 @@ jobs:
with:
name: flight-playwright-report
path: fixtures/flight/playwright-report
if-no-files-found: warn
- name: Archive Flight fixture artifacts
uses: actions/upload-artifact@v4
with:
name: flight-test-results
path: fixtures/flight/test-results
if-no-files-found: ignore
# ----- DEVTOOLS -----
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -698,19 +514,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -728,7 +539,6 @@ jobs:
with:
name: react-devtools-${{ matrix.browser }}-extension
path: build/devtools/${{ matrix.browser }}-extension.zip
if-no-files-found: error
merge_devtools_artifacts:
name: Merge DevTools artifacts
@@ -743,7 +553,7 @@ jobs:
run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -755,19 +565,14 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -786,9 +591,6 @@ jobs:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
name: Run sizebot
needs: [build_and_lint]
permissions:
# We use github.token to download the build artifact from a previous runtime_build_and_test.yml run
actions: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -800,36 +602,30 @@ jobs:
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
working-directory: scripts/release
- 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
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- name: Restore archived build for PR
uses: actions/download-artifact@v4
with:
@@ -842,11 +638,10 @@ jobs:
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- run: node ./scripts/tasks/danger
- name: Archive sizebot results
uses: actions/upload-artifact@v4
with:
name: sizebot-message
path: sizebot-message.md
if-no-files-found: ignore

View File

@@ -16,13 +16,6 @@ on:
required: true
default: false
type: boolean
dry_run:
description: Perform a dry run (run everything except push)
required: true
default: false
type: boolean
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -32,40 +25,6 @@ env:
jobs:
download_artifacts:
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
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
- name: Display structure of build
run: ls -R build
- name: Archive build
uses: actions/upload-artifact@v4
with:
name: build
path: build/
if-no-files-found: error
process_artifacts:
runs-on: ubuntu-latest
needs: [download_artifacts]
outputs:
www_branch_count: ${{ steps.check_branches.outputs.www_branch_count }}
fbsource_branch_count: ${{ steps.check_branches.outputs.fbsource_branch_count }}
@@ -105,11 +64,27 @@ jobs:
run: |
echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT"
echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT"
- name: Restore downloaded build
uses: actions/download-artifact@v4
- uses: actions/setup-node@v4
with:
name: build
path: build
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
name: yarn install (react)
- run: yarn install --frozen-lockfile
name: yarn install (scripts/release)
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
- name: Display structure of build
run: ls -R build
- name: Strip @license from eslint plugin and react-refresh
@@ -132,10 +107,9 @@ jobs:
mkdir ./compiled/facebook-www/__test_utils__
mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
# Move eslint-plugin-react-hooks into eslint-plugin-react-hooks
mkdir ./compiled/eslint-plugin-react-hooks
# Move eslint-plugin-react-hooks into facebook-www
mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
./compiled/eslint-plugin-react-hooks/index.js
./compiled/facebook-www/eslint-plugin-react-hooks.js
# Move unstable_server-external-runtime.js into facebook-www
mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
@@ -173,9 +147,9 @@ jobs:
ls -R ./compiled-rn
- name: Add REVISION files
run: |
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled/facebook-www/REVISION
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
echo ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
echo ${{ github.sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
- name: "Get current version string"
id: get_current_version
run: |
@@ -192,20 +166,15 @@ jobs:
with:
name: compiled
path: compiled/
if-no-files-found: error
- uses: actions/upload-artifact@v4
with:
name: compiled-rn
path: compiled-rn/
if-no-files-found: error
commit_www_artifacts:
needs: [download_artifacts, process_artifacts]
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.www_branch_count == '0')
needs: download_artifacts
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.www_branch_count == '0')
runs-on: ubuntu-latest
permissions:
# Used to push a commit to builds/facebook-www
contents: write
steps:
- uses: actions/checkout@v4
with:
@@ -217,12 +186,12 @@ jobs:
name: compiled
path: compiled/
- name: Revert version changes
if: needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != ''
if: needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != ''
env:
CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
run: |
echo "Reverting $CURRENT_VERSION_CLASSIC to $LAST_VERSION_CLASSIC"
grep -rl "$CURRENT_VERSION_CLASSIC" ./compiled || echo "No files found with $CURRENT_VERSION_CLASSIC"
@@ -252,12 +221,12 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_classic != '' && needs.process_artifacts.outputs.last_version_modern != '')
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_classic != '' && needs.download_artifacts.outputs.last_version_modern != '')
env:
CURRENT_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.process_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.process_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.process_artifacts.outputs.last_version_modern }}
CURRENT_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.current_version_classic }}
CURRENT_VERSION_MODERN: ${{ needs.download_artifacts.outputs.current_version_modern }}
LAST_VERSION_CLASSIC: ${{ needs.download_artifacts.outputs.last_version_classic }}
LAST_VERSION_MODERN: ${{ needs.download_artifacts.outputs.last_version_modern }}
run: |
echo "Re-applying $LAST_VERSION_CLASSIC to $CURRENT_VERSION_CLASSIC"
grep -rl "$LAST_VERSION_CLASSIC" ./compiled || echo "No files found with $LAST_VERSION_CLASSIC"
@@ -271,31 +240,24 @@ jobs:
- name: Will commit these changes
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
echo ":"
git status -u
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-www
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
commit_fbsource_artifacts:
needs: [download_artifacts, process_artifacts]
permissions:
# Used to push a commit to builds/facebook-fbsource
contents: write
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.process_artifacts.outputs.fbsource_branch_count == '0')
needs: download_artifacts
if: inputs.force == true || (github.ref == 'refs/heads/main' && needs.download_artifacts.outputs.fbsource_branch_count == '0')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -308,10 +270,10 @@ jobs:
name: compiled-rn
path: compiled-rn/
- name: Revert version changes
if: needs.process_artifacts.outputs.last_version_rn != ''
if: needs.download_artifacts.outputs.last_version_rn != ''
env:
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
run: |
echo "Reverting $CURRENT_VERSION to $LAST_VERSION"
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "No files found with $CURRENT_VERSION"
@@ -337,10 +299,10 @@ jobs:
echo "should_commit=false" >> "$GITHUB_OUTPUT"
fi
- name: Re-apply version changes
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.process_artifacts.outputs.last_version_rn != '')
if: inputs.force == true || (steps.check_should_commit.outputs.should_commit == 'true' && needs.download_artifacts.outputs.last_version_rn != '')
env:
CURRENT_VERSION: ${{ needs.process_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.process_artifacts.outputs.last_version_rn }}
CURRENT_VERSION: ${{ needs.download_artifacts.outputs.current_version_rn }}
LAST_VERSION: ${{ needs.download_artifacts.outputs.last_version_rn }}
run: |
echo "Re-applying $LAST_VERSION to $CURRENT_VERSION"
grep -rl "$LAST_VERSION" ./compiled-rn || echo "No files found with $LAST_VERSION"
@@ -447,19 +409,15 @@ jobs:
run: |
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-fbsource
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true

View File

@@ -7,14 +7,9 @@ on:
- compiler/**
- .github/workflows/compiler_**.yml
permissions: {}
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
@@ -24,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}

View File

@@ -7,8 +7,6 @@ on:
paths-ignore:
- compiler/**
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -37,29 +35,22 @@ jobs:
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
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') }}
path: "node_modules"
key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --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

@@ -8,8 +8,6 @@ on:
- main
workflow_dispatch:
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

@@ -14,26 +14,20 @@ on:
required: true
type: string
secrets:
GH_TOKEN:
required: true
NPM_TOKEN:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
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
@@ -45,16 +39,14 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- 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 }}

View File

@@ -6,8 +6,6 @@ on:
prerelease_commit_sha:
required: true
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -16,9 +14,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 +30,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 +45,3 @@ jobs:
dist_tag: experimental
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -5,8 +5,6 @@ on:
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
- cron: 10 16 * * 1,2,3,4,5
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -14,23 +12,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
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
@@ -42,4 +33,3 @@ jobs:
dist_tag: experimental
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -31,12 +31,11 @@ on:
type: boolean
default: false
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
@@ -45,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
uses: tsickert/discord-webhook@v6.0.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.sender.login }}
@@ -72,15 +71,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- if: '${{ inputs.only_packages }}'
name: 'Prepare ${{ inputs.only_packages }} from NPM'

View File

@@ -6,12 +6,14 @@ 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 }}
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -20,12 +22,10 @@ env:
jobs:
check_maintainer:
runs-on: ubuntu-latest
permissions:
# We fetch the contents of the MAINTAINERS file
contents: read
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 +33,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

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

View File

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

View File

@@ -5,8 +5,6 @@ on:
branches:
- 'builds/facebook-**'
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -15,9 +13,6 @@ env:
jobs:
close_pr:
runs-on: ubuntu-latest
permissions:
# Used to create a review and close PRs
pull-requests: write
steps:
- name: Close PR
uses: actions/github-script@v7

View File

@@ -3,8 +3,6 @@ name: (Shared) Label Core Team PRs
on:
pull_request_target:
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
@@ -13,9 +11,6 @@ env:
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
permissions:
# Used by check_maintainer
contents: read
with:
actor: ${{ github.event.pull_request.user.login }}
@@ -23,11 +18,6 @@ jobs:
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
runs-on: ubuntu-latest
needs: check_maintainer
permissions:
# Used to add labels on issues
issues: write
# Used to add labels on PRs
pull-requests: write
steps:
- name: Label PR as React Core Team
uses: actions/github-script@v7

View File

@@ -5,8 +5,6 @@ on:
branches: [main]
pull_request:
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
@@ -29,15 +27,12 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn prettier-check
eslint:
@@ -52,15 +47,12 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: node ./scripts/tasks/eslint
check_license:
@@ -75,15 +67,12 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/check_license.sh
test_print_warnings:
@@ -98,13 +87,10 @@ jobs:
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/ci/test_print_warnings.sh

View File

@@ -6,8 +6,6 @@ on:
- cron: '0 * * * *'
workflow_dispatch:
permissions: {}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles

View File

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

View File

@@ -7,8 +7,6 @@ module.exports = {
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-flow-strip-types',
['@babel/plugin-transform-class-properties', {loose: true}],
'@babel/plugin-transform-classes',
],
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],

View File

@@ -9,13 +9,6 @@ import {expect, test} from '@playwright/test';
import {encodeStore, type Store} from '../../lib/stores';
import {format} from 'prettier';
function isMonacoLoaded(): boolean {
return (
typeof window['MonacoEnvironment'] !== 'undefined' &&
window['__MONACO_LOADED__'] === true
);
}
function formatPrint(data: Array<string>): Promise<string> {
return format(data.join(''), {parser: 'babel'});
}
@@ -112,7 +105,6 @@ function nonReactFn() {
test('editor should open successfully', async ({page}) => {
await page.goto(`/`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await page.screenshot({
fullPage: true,
path: 'test-results/00-fresh-page.png',
@@ -128,7 +120,6 @@ test('editor should compile from hash successfully', async ({page}) => {
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
// User input from hash compiles
await page.screenshot({
@@ -152,7 +143,6 @@ test('reset button works', async ({page}) => {
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
// Reset button works
page.on('dialog', dialog => dialog.accept());
@@ -176,7 +166,6 @@ TEST_CASE_INPUTS.forEach((t, idx) =>
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await page.screenshot({
fullPage: true,
path: `test-results/03-0${idx}-${t.name}.png`,

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';
@@ -89,9 +89,6 @@ export default function Input({errors, language}: Props): JSX.Element {
_: editor.IStandaloneCodeEditor,
monaco: Monaco,
) => void = (_, monaco) => {
if (typeof window !== 'undefined') {
window['__MONACO_LOADED__'] = true;
}
setMonaco(monaco);
const tscOptions = {

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",
@@ -12,7 +12,7 @@
"vercel-build": "yarn build",
"start": "next start",
"lint": "next lint",
"test": "playwright test --workers=4"
"test": "playwright test"
},
"dependencies": {
"@babel/core": "^7.18.9",
@@ -22,9 +22,9 @@
"@babel/plugin-transform-block-scoping": "^7.18.9",
"@babel/plugin-transform-modules-commonjs": "^7.18.9",
"@babel/preset-react": "^7.18.9",
"@babel/preset-typescript": "^7.26.0",
"@babel/preset-typescript": "^7.18.9",
"@babel/traverse": "^7.18.9",
"@babel/types": "7.26.3",
"@babel/types": "7.18.9",
"@heroicons/react": "^1.0.6",
"@monaco-editor/react": "^4.4.6",
"@playwright/test": "^1.42.1",
@@ -39,13 +39,13 @@
"prettier": "^3.3.3",
"pretty-format": "^29.3.1",
"re-resizable": "^6.9.16",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react": "19.0.0-rc-77b637d6-20241016",
"react-dom": "19.0.0-rc-77b637d6-20241016"
},
"devDependencies": {
"@types/node": "18.11.9",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"concurrently": "^7.4.0",
@@ -55,5 +55,9 @@
"postcss": "^8.4.31",
"tailwindcss": "^3.2.4",
"wait-on": "^7.2.0"
},
"resolutions": {
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
}
}

View File

@@ -23,7 +23,7 @@ export default defineConfig({
// Test directory
testDir: path.join(__dirname, '__tests__/e2e'),
// If a test fails, retry it additional 2 times
retries: 3,
retries: 2,
// Artifacts folder where screenshots, videos, and traces are stored.
outputDir: 'test-results/',
// Note: we only use text snapshots, so its safe to omit the host environment name

View File

@@ -23,15 +23,6 @@
"@babel/highlight" "^7.24.7"
picocolors "^1.0.0"
"@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
dependencies:
"@babel/helper-validator-identifier" "^7.25.9"
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/compat-data@^7.25.2":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb"
@@ -68,17 +59,6 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
"@babel/generator@^7.26.10":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7"
integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==
dependencies:
"@babel/parser" "^7.26.10"
"@babel/types" "^7.26.10"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab"
@@ -86,13 +66,6 @@
dependencies:
"@babel/types" "^7.24.7"
"@babel/helper-annotate-as-pure@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==
dependencies:
"@babel/types" "^7.25.9"
"@babel/helper-compilation-targets@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c"
@@ -104,26 +77,26 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.25.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==
"@babel/helper-create-class-features-plugin@^7.25.0":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14"
integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==
dependencies:
"@babel/helper-annotate-as-pure" "^7.25.9"
"@babel/helper-member-expression-to-functions" "^7.25.9"
"@babel/helper-optimise-call-expression" "^7.25.9"
"@babel/helper-replace-supers" "^7.26.5"
"@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
"@babel/traverse" "^7.26.9"
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-member-expression-to-functions" "^7.24.8"
"@babel/helper-optimise-call-expression" "^7.24.7"
"@babel/helper-replace-supers" "^7.25.0"
"@babel/helper-skip-transparent-expression-wrappers" "^7.24.7"
"@babel/traverse" "^7.25.4"
semver "^6.3.1"
"@babel/helper-member-expression-to-functions@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3"
integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==
"@babel/helper-member-expression-to-functions@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6"
integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==
dependencies:
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/traverse" "^7.24.8"
"@babel/types" "^7.24.8"
"@babel/helper-module-imports@^7.24.7":
version "7.24.7"
@@ -133,14 +106,6 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-module-imports@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
dependencies:
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6"
@@ -151,40 +116,26 @@
"@babel/helper-validator-identifier" "^7.24.7"
"@babel/traverse" "^7.25.2"
"@babel/helper-module-transforms@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
"@babel/helper-optimise-call-expression@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f"
integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==
dependencies:
"@babel/helper-module-imports" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@babel/helper-optimise-call-expression@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e"
integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==
dependencies:
"@babel/types" "^7.25.9"
"@babel/types" "^7.24.7"
"@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
"@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
"@babel/helper-replace-supers@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d"
integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
"@babel/helper-replace-supers@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9"
integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.25.9"
"@babel/helper-optimise-call-expression" "^7.25.9"
"@babel/traverse" "^7.26.5"
"@babel/helper-member-expression-to-functions" "^7.24.8"
"@babel/helper-optimise-call-expression" "^7.24.7"
"@babel/traverse" "^7.25.0"
"@babel/helper-simple-access@^7.24.7":
version "7.24.7"
@@ -194,39 +145,29 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9"
integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==
"@babel/helper-skip-transparent-expression-wrappers@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9"
integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==
dependencies:
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-string-parser@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
"@babel/helper-string-parser@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d"
integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
"@babel/helper-validator-identifier@^7.24.7":
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
"@babel/helper-validator-identifier@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
"@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d"
integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==
"@babel/helper-validator-option@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
"@babel/helpers@^7.25.0":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60"
@@ -252,13 +193,6 @@
dependencies:
"@babel/types" "^7.25.6"
"@babel/parser@^7.26.10", "@babel/parser@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
dependencies:
"@babel/types" "^7.26.10"
"@babel/plugin-syntax-jsx@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d"
@@ -266,27 +200,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-syntax-jsx@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290"
integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-syntax-typescript@^7.18.9":
"@babel/plugin-syntax-typescript@^7.18.9", "@babel/plugin-syntax-typescript@^7.24.7":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff"
integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==
dependencies:
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-syntax-typescript@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399"
integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-block-scoping@^7.18.9":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz#23a6ed92e6b006d26b1869b1c91d1b917c2ea2ac"
@@ -294,7 +214,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-transform-modules-commonjs@^7.18.9":
"@babel/plugin-transform-modules-commonjs@^7.18.9", "@babel/plugin-transform-modules-commonjs@^7.24.7":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c"
integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==
@@ -303,14 +223,6 @@
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-simple-access" "^7.24.7"
"@babel/plugin-transform-modules-commonjs@^7.25.9":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb"
integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==
dependencies:
"@babel/helper-module-transforms" "^7.26.0"
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-react-display-name@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz#9caff79836803bc666bcfe210aeb6626230c293b"
@@ -344,16 +256,16 @@
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-transform-typescript@^7.25.9":
version "7.26.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz#2e9caa870aa102f50d7125240d9dbf91334b0950"
integrity sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==
"@babel/plugin-transform-typescript@^7.24.7":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz#237c5d10de6d493be31637c6b9fa30b6c5461add"
integrity sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==
dependencies:
"@babel/helper-annotate-as-pure" "^7.25.9"
"@babel/helper-create-class-features-plugin" "^7.25.9"
"@babel/helper-plugin-utils" "^7.26.5"
"@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
"@babel/plugin-syntax-typescript" "^7.25.9"
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-create-class-features-plugin" "^7.25.0"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-skip-transparent-expression-wrappers" "^7.24.7"
"@babel/plugin-syntax-typescript" "^7.24.7"
"@babel/preset-react@^7.18.9":
version "7.24.7"
@@ -367,16 +279,16 @@
"@babel/plugin-transform-react-jsx-development" "^7.24.7"
"@babel/plugin-transform-react-pure-annotations" "^7.24.7"
"@babel/preset-typescript@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d"
integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==
"@babel/preset-typescript@^7.18.9":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz#66cd86ea8f8c014855671d5ea9a737139cbbfef1"
integrity sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/helper-validator-option" "^7.25.9"
"@babel/plugin-syntax-jsx" "^7.25.9"
"@babel/plugin-transform-modules-commonjs" "^7.25.9"
"@babel/plugin-transform-typescript" "^7.25.9"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/helper-validator-option" "^7.24.7"
"@babel/plugin-syntax-jsx" "^7.24.7"
"@babel/plugin-transform-modules-commonjs" "^7.24.7"
"@babel/plugin-transform-typescript" "^7.24.7"
"@babel/runtime@^7.21.0":
version "7.25.6"
@@ -394,16 +306,7 @@
"@babel/parser" "^7.25.0"
"@babel/types" "^7.25.0"
"@babel/template@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2":
"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.4":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41"
integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==
@@ -416,34 +319,22 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380"
integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==
"@babel/types@7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f"
integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/generator" "^7.26.10"
"@babel/parser" "^7.26.10"
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.10"
debug "^4.3.1"
globals "^11.1.0"
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
"@babel/types@7.26.3", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==
"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6"
integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259"
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/helper-string-parser" "^7.24.8"
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@emnapi/runtime@^1.2.0":
version "1.3.1"
@@ -842,15 +733,17 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
"@types/react-dom@^19.0.0":
version "19.0.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.4.tgz#bedba97f9346bd4c0fe5d39e689713804ec9ac89"
integrity sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==
"@types/react-dom@npm:types-react-dom@19.0.0-rc.1":
version "19.0.0-rc.1"
resolved "https://registry.yarnpkg.com/types-react-dom/-/types-react-dom-19.0.0-rc.1.tgz#1d544d02c5df2a82d87c2eff979afa2e21a8317a"
integrity sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ==
dependencies:
"@types/react" "*"
"@types/react@^19.0.0":
version "19.0.10"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.10.tgz#d0c66dafd862474190fe95ce11a68de69ed2b0eb"
integrity sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==
"@types/react@*", "@types/react@npm:types-react@19.0.0-rc.1":
version "19.0.0-rc.1"
resolved "https://registry.yarnpkg.com/types-react/-/types-react-19.0.0-rc.1.tgz#576d1a702f6d0cc5b24813a293913e5cbfeaa647"
integrity sha512-RshndUfqTW6K3STLPis8BtAYCGOkMbtvYsi90gmVNDZBXUyUc5juf2PE9LfS/JmOlUIRO8cWTS/1MTnmhjDqyQ==
dependencies:
csstype "^3.0.2"
@@ -2575,11 +2468,6 @@ jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
@@ -3137,12 +3025,12 @@ re-resizable@^6.9.16:
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.10.0.tgz#d684a096ab438f1a93f59ad3a580a206b0ce31ee"
integrity sha512-hysSK0xmA5nz24HBVztlk4yCqCLCvS32E6ZpWxVKop9x3tqCa4yAj1++facrmkOf62JsJHjmjABdKxXofYioCw==
react-dom@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
react-dom@19.0.0-rc-77b637d6-20241016:
version "19.0.0-rc-77b637d6-20241016"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0-rc-77b637d6-20241016.tgz#71afcba4abbd81a73e85086029202423cf85355e"
integrity sha512-xp5LvY+O6uvg0fNbSMyMXe0kbgzw6qn0mbqrdXStm4LBpFeMswLZ+XSNr+eJ0HyIiWrCw0rrXaVdqOxc9wtdKA==
dependencies:
scheduler "^0.25.0"
scheduler "0.25.0-rc-77b637d6-20241016"
react-is@^16.13.1:
version "16.13.1"
@@ -3154,10 +3042,10 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==
react@19.0.0-rc-77b637d6-20241016:
version "19.0.0-rc-77b637d6-20241016"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0-rc-77b637d6-20241016.tgz#9e20f116d0195979f192537e00a0fa1687680319"
integrity sha512-9A+i+PGSH/P4MezU4w38K9cbJuy0pzsXoPjPWIv6TQGCFmc5qCzC+8yce8dzfSEF1KJgCF2CLc5qtq/ePfiVqg==
read-cache@^1.0.0:
version "1.0.0"
@@ -3279,10 +3167,10 @@ safe-regex-test@^1.0.3:
es-errors "^1.3.0"
is-regex "^1.1.4"
scheduler@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015"
integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==
scheduler@0.25.0-rc-77b637d6-20241016:
version "0.25.0-rc-77b637d6-20241016"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0-rc-77b637d6-20241016.tgz#ab8f8d1cccc9668946caaa1103acdcdb5c871122"
integrity sha512-R5NTrZXJaW4Dj2jHmad2MTehpFq4yUQOxRKDNV7clP1q4Pz6RtUIcofdPnGUWM0krlJAw8DHd/4jT41pFK4iEg==
semver@^6.3.1:
version "6.3.1"
@@ -3629,6 +3517,11 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"

View File

@@ -1,6 +1,6 @@
# React Compiler Development Guide
Note: for general notes about contributing, see the [CONTRIBUTING.md](../../CONTRIBUTING.md).
Note: for general notes about contributing, see the [CONTRIBUTING.md](../CONTRIBUTING.md).
## Compiler Development

View File

@@ -26,7 +26,6 @@
"react-is": "0.0.0-experimental-4beb1fd8-20241118"
},
"devDependencies": {
"@babel/types": "^7.26.0",
"@tsconfig/strictest": "^2.0.5",
"concurrently": "^7.4.0",
"esbuild": "^0.25.0",
@@ -38,15 +37,12 @@
"prettier-plugin-hermes-parser": "^0.26.0",
"prompt-promise": "^1.0.3",
"rimraf": "^5.0.10",
"to-fast-properties": "^2.0.0",
"tsup": "^8.4.0",
"typescript": "^5.4.3",
"wait-on": "^7.2.0",
"yargs": "^17.7.2"
},
"resolutions": {
"rimraf": "5.0.10",
"@babel/types": "7.26.3"
"rimraf": "5.0.10"
},
"packageManager": "yarn@1.22.22"
}

View File

@@ -9,18 +9,18 @@
"!*.tsbuildinfo"
],
"scripts": {
"build": "rimraf dist && tsup",
"build": "rimraf dist && scripts/build.js",
"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": "scripts/build.js --watch"
},
"dependencies": {
"@babel/types": "^7.26.0"
"@babel/types": "^7.19.0"
},
"devDependencies": {
"@babel/core": "^7.2.0",

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env node
/**
* 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.
*/
const esbuild = require('esbuild');
const yargs = require('yargs');
const path = require('path');
const argv = yargs(process.argv.slice(2))
.options('w', {
alias: 'watch',
default: false,
type: 'boolean',
})
.parse();
const config = {
entryPoints: [path.join(__dirname, '../src/index.ts')],
outfile: path.join(__dirname, '../dist/index.js'),
bundle: true,
external: ['@babel/types'],
format: 'cjs',
platform: 'node',
banner: {
js: `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @lightSyntaxTransform
* @noflow
* @nolint
* @preventMunge
* @preserve-invariant-messages
*/
"use no memo";`,
},
};
async function main() {
if (argv.w) {
const ctx = await esbuild.context(config);
await ctx.watch();
console.log('watching for changes...');
} else {
await esbuild.build({
sourcemap: true,
minify: false,
...config,
});
}
}
main();

View File

@@ -7,4 +7,7 @@
const makeE2EConfig = require('../jest/makeE2EConfig');
module.exports = makeE2EConfig('e2e with forget', true);
const config = makeE2EConfig('e2e with forget', true);
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
module.exports = config;

View File

@@ -5,16 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/
import {jsx} from '@babel/plugin-syntax-jsx';
import babelJest from 'babel-jest';
import {
validateEnvironmentConfig,
EnvironmentConfig,
} from 'babel-plugin-react-compiler';
import {compile} from 'babel-plugin-react-compiler';
import {execSync} from 'child_process';
import type {NodePath, Visitor} from '@babel/traverse';
import type {CallExpression} from '@babel/types';
import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
import type {CallExpression, FunctionDeclaration} from '@babel/types';
import * as t from '@babel/types';
import {
EnvironmentConfig,
validateEnvironmentConfig,
} from 'babel-plugin-react-compiler';
import {basename} from 'path';
/**
* -- IMPORTANT --
@@ -25,19 +28,10 @@ import BabelPluginReactCompiler from 'babel-plugin-react-compiler';
const e2eTransformerCacheKey = 1;
const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
enableAssumeHooksFollowRulesOfReact: true,
enableFunctionOutlining: false,
});
const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
const compilerCacheKey = execSync(
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
)
.toString()
.trim();
if (debugMode) {
console.log('cachebreaker', compilerCacheKey);
}
module.exports = (useForget: boolean) => {
function createTransformer() {
return babelJest.createTransformer({
@@ -48,14 +42,15 @@ module.exports = (useForget: boolean) => {
plugins: [
useForget
? [
BabelPluginReactCompiler,
ReactForgetFunctionTransform,
{
environment: forgetOptions,
/*
* Jest hashes the babel config as a cache breaker.
* (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
*/
compilerCacheKey,
compilerCacheKey: execSync(
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
).toString(),
transformOptionsCacheKey: forgetOptions,
e2eTransformerCacheKey,
},
@@ -110,3 +105,103 @@ module.exports = (useForget: boolean) => {
createTransformer,
};
};
// Mostly copied from react/scripts/babel/transform-forget.js
function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
let isReactComponent = false;
let hasNoUseForgetDirective = false;
/*
* React components start with an upper case letter,
* React hooks start with `use`
*/
if (
fn.node.id == null ||
(fn.node.id.name[0].toUpperCase() !== fn.node.id.name[0] &&
!/^use[A-Z0-9]/.test(fn.node.id.name))
) {
return false;
}
fn.traverse({
DirectiveLiteral(path) {
if (path.node.value === 'use no forget') {
hasNoUseForgetDirective = true;
}
},
JSX(path) {
// Is there is a JSX node created in the current function context?
if (path.scope.getFunctionParent()?.path.node === fn.node) {
isReactComponent = true;
}
},
CallExpression(path) {
// Is there hook usage?
if (
path.node.callee.type === 'Identifier' &&
!/^use[A-Z0-9]/.test(path.node.callee.name)
) {
isReactComponent = true;
}
},
});
if (hasNoUseForgetDirective) {
return false;
}
return isReactComponent;
}
function ReactForgetFunctionTransform() {
const compiledFns = new Set();
const visitor = {
FunctionDeclaration(fn: NodePath<FunctionDeclaration>, state: any): void {
if (compiledFns.has(fn.node)) {
return;
}
if (!isReactComponentLike(fn)) {
return;
}
if (debugMode) {
const filename = basename(state.file.opts.filename);
if (fn.node.loc && fn.node.id) {
console.log(
` Compiling ${filename}:${fn.node.loc.start.line}:${fn.node.loc.start.column} ${fn.node.id.name}`,
);
} else {
console.log(` Compiling ${filename} ${fn.node.id?.name}`);
}
}
const compiled = compile(
fn,
forgetOptions,
'Other',
'_c',
null,
null,
null,
);
compiledFns.add(compiled);
const fun = t.functionDeclaration(
compiled.id,
compiled.params,
compiled.body,
compiled.generator,
compiled.async,
);
fn.replaceWith(fun);
fn.skip();
},
};
return {
name: 'react-forget-e2e',
inherits: jsx,
visitor,
};
}

View File

@@ -0,0 +1,16 @@
/**
* 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.
*/
const ReactCompilerRuntime = require('react/compiler-runtime');
/*
* Our e2e babel transform currently only compiles functions, not programs.
* As a result, our e2e transpiled code does not contain an import for the
* memo cache function. As a temporary hack, we add a `_c` global, which is
* the name that is used for the import by default.
*/
globalThis._c = ReactCompilerRuntime.c;

View File

@@ -11,7 +11,6 @@ import {
injectReanimatedFlag,
pipelineUsesReanimatedPlugin,
} from '../Entrypoint/Reanimated';
import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences';
const ENABLE_REACT_COMPILER_TIMINGS =
process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1';
@@ -62,19 +61,12 @@ export default function BabelPluginReactCompiler(
},
};
}
const result = compileProgram(prog, {
compileProgram(prog, {
opts,
filename: pass.filename ?? null,
comments: pass.file.ast.comments ?? [],
code: pass.file.code,
});
validateNoUntransformedReferences(
prog,
pass.filename ?? null,
opts.logger,
opts.environment,
result,
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {
detail: 'BabelPlugin:Program:end',

View File

@@ -6,7 +6,6 @@
*/
import type {SourceLocation} from './HIR';
import {Err, Ok, Result} from './Utils/Result';
import {assertExhaustive} from './Utils/utils';
export enum ErrorSeverity {
@@ -189,7 +188,6 @@ export class CompilerError extends Error {
constructor(...args: Array<any>) {
super(...args);
this.name = 'ReactCompilerError';
this.details = [];
}
override get message(): string {
@@ -199,10 +197,7 @@ export class CompilerError extends Error {
override set message(_message: string) {}
override toString(): string {
if (Array.isArray(this.details)) {
return this.details.map(detail => detail.toString()).join('\n\n');
}
return this.name;
return this.details.map(detail => detail.toString()).join('\n\n');
}
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
@@ -225,10 +220,6 @@ export class CompilerError extends Error {
return this.details.length > 0;
}
asResult(): Result<void, CompilerError> {
return this.hasErrors() ? Err(this) : Ok(undefined);
}
/*
* An error is critical if it means the compiler has entered into a broken state and cannot
* continue safely. Other expected errors such as Todos mean that we can skip over that component

View File

@@ -7,122 +7,8 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {CompilerError} from '../CompilerError';
import {ProgramContext} from './Imports';
import {ExternalFunction} from '..';
import {PluginOptions} from './Options';
/**
* Gating rewrite for function declarations which are referenced before their
* declaration site.
*
* ```js
* // original
* export default React.memo(Foo);
* function Foo() { ... }
*
* // React compiler optimized + gated
* import {gating} from 'myGating';
* export default React.memo(Foo);
* const gating_result = gating(); <- inserted
* function Foo_optimized() {} <- inserted
* function Foo_unoptimized() {} <- renamed from Foo
* function Foo() { <- inserted function, which can be hoisted by JS engines
* if (gating_result) return Foo_optimized();
* else return Foo_unoptimized();
* }
* ```
*/
function insertAdditionalFunctionDeclaration(
fnPath: NodePath<t.FunctionDeclaration>,
compiled: t.FunctionDeclaration,
programContext: ProgramContext,
gatingFunctionIdentifierName: string,
): void {
const originalFnName = fnPath.node.id;
const originalFnParams = fnPath.node.params;
const compiledParams = fnPath.node.params;
/**
* Note that other than `export default function() {}`, all other function
* declarations must have a binding identifier. Since default exports cannot
* be referenced, it's safe to assume that all function declarations passed
* here will have an identifier.
* https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-function-definitions
*/
CompilerError.invariant(originalFnName != null && compiled.id != null, {
reason:
'Expected function declarations that are referenced elsewhere to have a named identifier',
loc: fnPath.node.loc ?? null,
});
CompilerError.invariant(originalFnParams.length === compiledParams.length, {
reason:
'Expected React Compiler optimized function declarations to have the same number of parameters as source',
loc: fnPath.node.loc ?? null,
});
const gatingCondition = t.identifier(
programContext.newUid(`${gatingFunctionIdentifierName}_result`),
);
const unoptimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_unoptimized`),
);
const optimizedFnName = t.identifier(
programContext.newUid(`${originalFnName.name}_optimized`),
);
/**
* Step 1: rename existing functions
*/
compiled.id.name = optimizedFnName.name;
fnPath.get('id').replaceInline(unoptimizedFnName);
/**
* Step 2: insert new function declaration
*/
const newParams: Array<t.Identifier | t.RestElement> = [];
const genNewArgs: Array<() => t.Identifier | t.SpreadElement> = [];
for (let i = 0; i < originalFnParams.length; i++) {
const argName = `arg${i}`;
if (originalFnParams[i].type === 'RestElement') {
newParams.push(t.restElement(t.identifier(argName)));
genNewArgs.push(() => t.spreadElement(t.identifier(argName)));
} else {
newParams.push(t.identifier(argName));
genNewArgs.push(() => t.identifier(argName));
}
}
// insertAfter called in reverse order of how nodes should appear in program
fnPath.insertAfter(
t.functionDeclaration(
originalFnName,
newParams,
t.blockStatement([
t.ifStatement(
gatingCondition,
t.returnStatement(
t.callExpression(
compiled.id,
genNewArgs.map(fn => fn()),
),
),
t.returnStatement(
t.callExpression(
unoptimizedFnName,
genNewArgs.map(fn => fn()),
),
),
),
]),
),
);
fnPath.insertBefore(
t.variableDeclaration('const', [
t.variableDeclarator(
gatingCondition,
t.callExpression(t.identifier(gatingFunctionIdentifierName), []),
),
]),
);
fnPath.insertBefore(compiled);
}
export function insertGatedFunctionDeclaration(
fnPath: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
@@ -131,65 +17,48 @@ export function insertGatedFunctionDeclaration(
| t.FunctionDeclaration
| t.ArrowFunctionExpression
| t.FunctionExpression,
programContext: ProgramContext,
gating: ExternalFunction,
referencedBeforeDeclaration: boolean,
gating: NonNullable<PluginOptions['gating']>,
): void {
const gatingImportedName = programContext.addImportSpecifier(gating).name;
if (referencedBeforeDeclaration && fnPath.isFunctionDeclaration()) {
CompilerError.invariant(compiled.type === 'FunctionDeclaration', {
reason: 'Expected compiled node type to match input type',
description: `Got ${compiled.type} but expected FunctionDeclaration`,
loc: fnPath.node.loc ?? null,
});
insertAdditionalFunctionDeclaration(
fnPath,
compiled,
programContext,
gatingImportedName,
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gating.importSpecifierName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
);
} else {
const gatingExpression = t.conditionalExpression(
t.callExpression(t.identifier(gatingImportedName), []),
buildFunctionExpression(compiled),
buildFunctionExpression(fnPath.node),
);
/*
* Convert function declarations to named variables *unless* this is an
* `export default function ...` since `export default const ...` is
* not supported. For that case we fall through to replacing w the raw
* conditional expression
*/
if (
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
fnPath.node.type === 'FunctionDeclaration' &&
fnPath.node.id != null
) {
fnPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(fnPath.node.id, gatingExpression),
]),
);
} else if (
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
fnPath.node.type !== 'ArrowFunctionExpression' &&
fnPath.node.id != null
) {
fnPath.insertAfter(
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
);
fnPath.parentPath.replaceWith(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(fnPath.node.id.name),
gatingExpression,
),
]),
);
} else {
fnPath.replaceWith(gatingExpression);
}
fnPath.replaceWith(gatingExpression);
}
}

View File

@@ -7,19 +7,9 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {Scope as BabelScope} from '@babel/traverse';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
EnvironmentConfig,
GeneratedSource,
NonLocalImportSpecifier,
} from '../HIR';
import {getOrInsertWith} from '../Utils/utils';
import {ExternalFunction, isHookName} from '../HIR/Environment';
import {Err, Ok, Result} from '../Utils/Result';
import {CompilerReactTarget} from './Options';
import {getReactCompilerRuntimeModule} from './Program';
import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR';
import {getOrInsertDefault} from '../Utils/utils';
export function validateRestrictedImports(
path: NodePath<t.Program>,
@@ -52,209 +42,50 @@ export function validateRestrictedImports(
}
}
export class ProgramContext {
/* Program and environment context */
scope: BabelScope;
reactRuntimeModule: string;
hookPattern: string | null;
// known generated or referenced identifiers in the program
knownReferencedNames: Set<string> = new Set();
// generated imports
imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
constructor(
program: NodePath<t.Program>,
reactRuntimeModule: CompilerReactTarget,
hookPattern: string | null,
) {
this.hookPattern = hookPattern;
this.scope = program.scope;
this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule);
}
isHookName(name: string): boolean {
if (this.hookPattern == null) {
return isHookName(name);
} else {
const match = new RegExp(this.hookPattern).exec(name);
return (
match != null && typeof match[1] === 'string' && isHookName(match[1])
);
}
}
hasReference(name: string): boolean {
return (
this.knownReferencedNames.has(name) ||
this.scope.hasBinding(name) ||
this.scope.hasGlobal(name) ||
this.scope.hasReference(name)
);
}
newUid(name: string): string {
/**
* Don't call babel's generateUid for known hook imports, as
* InferTypes might eventually type `HookKind` based on callee naming
* convention and `_useFoo` is not named as a hook.
*
* Local uid generation is susceptible to check-before-use bugs since we're
* checking for naming conflicts / references long before we actually insert
* the import. (see similar logic in HIRBuilder:resolveBinding)
*/
let uid;
if (this.isHookName(name)) {
uid = name;
let i = 0;
while (this.hasReference(uid)) {
this.knownReferencedNames.add(uid);
uid = `${name}_${i++}`;
}
} else if (!this.hasReference(name)) {
uid = name;
} else {
uid = this.scope.generateUid(name);
}
this.knownReferencedNames.add(uid);
return uid;
}
addMemoCacheImport(): NonLocalImportSpecifier {
return this.addImportSpecifier(
{
source: this.reactRuntimeModule,
importSpecifierName: 'c',
},
'_c',
);
}
/**
*
* @param externalFunction
* @param nameHint if defined, will be used as the name of the import specifier
* @returns
*/
addImportSpecifier(
{source: module, importSpecifierName: specifier}: ExternalFunction,
nameHint?: string,
): NonLocalImportSpecifier {
const maybeBinding = this.imports.get(module)?.get(specifier);
if (maybeBinding != null) {
return {...maybeBinding};
}
const binding: NonLocalImportSpecifier = {
kind: 'ImportSpecifier',
name: this.newUid(nameHint ?? specifier),
module,
imported: specifier,
};
getOrInsertWith(this.imports, module, () => new Map()).set(specifier, {
...binding,
});
return binding;
}
addNewReference(name: string): void {
this.knownReferencedNames.add(name);
}
assertGlobalBinding(
name: string,
localScope?: BabelScope,
): Result<void, CompilerError> {
const scope = localScope ?? this.scope;
if (!scope.hasReference(name) && !scope.hasBinding(name)) {
return Ok(undefined);
}
const error = new CompilerError();
error.push({
severity: ErrorSeverity.Todo,
reason: 'Encountered conflicting global in generated program',
description: `Conflict from local binding ${name}`,
loc: scope.getBinding(name)?.path.node.loc ?? null,
suggestions: null,
});
return Err(error);
}
}
function getExistingImports(
program: NodePath<t.Program>,
): Map<string, NodePath<t.ImportDeclaration>> {
const existingImports = new Map<string, NodePath<t.ImportDeclaration>>();
program.traverse({
ImportDeclaration(path) {
if (isNonNamespacedImport(path)) {
existingImports.set(path.node.source.value, path);
}
},
});
return existingImports;
}
export function addImportsToProgram(
path: NodePath<t.Program>,
programContext: ProgramContext,
importList: Array<ExternalFunction>,
): void {
const existingImports = getExistingImports(path);
const identifiers: Set<string> = new Set();
const sortedImports: Map<string, Array<string>> = new Map();
for (const {importSpecifierName, source} of importList) {
/*
* Codegen currently does not rename import specifiers, so we do additional
* validation here
*/
CompilerError.invariant(identifiers.has(importSpecifierName) === false, {
reason: `Encountered conflicting import specifier for ${importSpecifierName} in Forget config.`,
description: null,
loc: GeneratedSource,
suggestions: null,
});
CompilerError.invariant(
path.scope.hasBinding(importSpecifierName) === false,
{
reason: `Encountered conflicting import specifiers for ${importSpecifierName} in generated program.`,
description: null,
loc: GeneratedSource,
suggestions: null,
},
);
identifiers.add(importSpecifierName);
const importSpecifierNameList = getOrInsertDefault(
sortedImports,
source,
[],
);
importSpecifierNameList.push(importSpecifierName);
}
const stmts: Array<t.ImportDeclaration> = [];
const sortedModules = [...programContext.imports.entries()].sort(([a], [b]) =>
a.localeCompare(b),
);
for (const [moduleName, importsMap] of sortedModules) {
for (const [specifierName, loweredImport] of importsMap) {
/**
* Assert that the import identifier hasn't already be declared in the program.
* Note: we use getBinding here since `Scope.hasBinding` pessimistically returns true
* for all allocated uids (from `Scope.getUid`)
*/
CompilerError.invariant(
path.scope.getBinding(loweredImport.name) == null,
{
reason:
'Encountered conflicting import specifiers in generated program',
description: `Conflict from import ${loweredImport.module}:(${loweredImport.imported} as ${loweredImport.name}).`,
loc: GeneratedSource,
suggestions: null,
},
);
CompilerError.invariant(
loweredImport.module === moduleName &&
loweredImport.imported === specifierName,
{
reason:
'Found inconsistent import specifier. This is an internal bug.',
description: `Expected import ${moduleName}:${specifierName} but found ${loweredImport.module}:${loweredImport.imported}`,
loc: GeneratedSource,
},
);
}
const sortedImport: Array<NonLocalImportSpecifier> = [
...importsMap.values(),
].sort(({imported: a}, {imported: b}) => a.localeCompare(b));
const importSpecifiers = sortedImport.map(specifier => {
return t.importSpecifier(
t.identifier(specifier.name),
t.identifier(specifier.imported),
);
for (const [source, importSpecifierNameList] of sortedImports) {
const importSpecifiers = importSpecifierNameList.map(name => {
const id = t.identifier(name);
return t.importSpecifier(id, id);
});
/**
* If an existing import of this module exists (ie `import { ... } from
* '<moduleName>'`), inject new imported specifiers into the list of
* destructured variables.
*/
const maybeExistingImports = existingImports.get(moduleName);
if (maybeExistingImports != null) {
maybeExistingImports.pushContainer('specifiers', importSpecifiers);
} else {
stmts.push(
t.importDeclaration(importSpecifiers, t.stringLiteral(moduleName)),
);
}
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
}
path.unshiftContainer('body', stmts);
}
@@ -262,12 +93,13 @@ export function addImportsToProgram(
/*
* Matches `import { ... } from <moduleName>;`
* but not `import * as React from <moduleName>;`
* `import type { Foo } from <moduleName>;`
*/
function isNonNamespacedImport(
importDeclPath: NodePath<t.ImportDeclaration>,
moduleName: string,
): boolean {
return (
importDeclPath.get('source').node.value === moduleName &&
importDeclPath
.get('specifiers')
.every(specifier => specifier.isImportSpecifier()) &&
@@ -275,3 +107,94 @@ function isNonNamespacedImport(
importDeclPath.node.importKind !== 'typeof'
);
}
function hasExistingNonNamespacedImportOfModule(
program: NodePath<t.Program>,
moduleName: string,
): boolean {
let hasExistingImport = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (isNonNamespacedImport(importDeclPath, moduleName)) {
hasExistingImport = true;
}
},
});
return hasExistingImport;
}
/*
* If an existing import of React exists (ie `import { ... } from '<moduleName>'`), inject useMemoCache
* into the list of destructured variables.
*/
function addMemoCacheFunctionSpecifierToExistingImport(
program: NodePath<t.Program>,
moduleName: string,
identifierName: string,
): boolean {
let didInsertUseMemoCache = false;
program.traverse({
ImportDeclaration(importDeclPath) {
if (
!didInsertUseMemoCache &&
isNonNamespacedImport(importDeclPath, moduleName)
) {
importDeclPath.pushContainer(
'specifiers',
t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
);
didInsertUseMemoCache = true;
}
},
});
return didInsertUseMemoCache;
}
export function updateMemoCacheFunctionImport(
program: NodePath<t.Program>,
moduleName: string,
useMemoCacheIdentifier: string,
): void {
/*
* If there isn't already an import of * as React, insert it so useMemoCache doesn't
* throw
*/
const hasExistingImport = hasExistingNonNamespacedImportOfModule(
program,
moduleName,
);
if (hasExistingImport) {
const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport(
program,
moduleName,
useMemoCacheIdentifier,
);
if (!didUpdateImport) {
throw new Error(
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`,
);
}
} else {
addMemoCacheFunctionImportDeclaration(
program,
moduleName,
useMemoCacheIdentifier,
);
}
}
function addMemoCacheFunctionImportDeclaration(
program: NodePath<t.Program>,
moduleName: string,
localName: string,
): void {
program.unshiftContainer(
'body',
t.importDeclaration(
[t.importSpecifier(t.identifier(localName), t.identifier('c'))],
t.stringLiteral(moduleName),
),
);
}

View File

@@ -12,7 +12,6 @@ import {
EnvironmentConfig,
ExternalFunction,
parseEnvironmentConfig,
tryParseExternalFunction,
} from '../HIR/Environment';
import {hasOwnProperty} from '../Utils/utils';
import {fromZodError} from 'zod-validation-error';
@@ -272,14 +271,6 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
parsedOptions[key] = parseTargetConfig(value);
break;
}
case 'gating': {
if (value == null) {
parsedOptions[key] = null;
} else {
parsedOptions[key] = tryParseExternalFunction(value);
}
break;
}
default: {
parsedOptions[key] = value;
}

View File

@@ -8,7 +8,7 @@
import {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import prettyFormat from 'pretty-format';
import {Logger, ProgramContext} from '.';
import {Logger} from '.';
import {
HIRFunction,
ReactiveFunction,
@@ -24,7 +24,6 @@ import {
pruneUnusedLabelsHIR,
} from '../HIR';
import {
CompilerMode,
Environment,
EnvironmentConfig,
ReactFunctionType,
@@ -100,9 +99,7 @@ import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHI
import {outlineJSX} from '../Optimization/OutlineJsx';
import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
import {CompilerError} from '..';
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
import {validateNoImpureFunctionsInRender} from '../Validation/ValiateNoImpureFunctionsInRender';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -116,8 +113,7 @@ function run(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -126,13 +122,12 @@ function run(
const env = new Environment(
func.scope,
fnType,
mode,
config,
contextIdentifiers,
logger,
filename,
code,
programContext,
useMemoCacheIdentifier,
);
env.logger?.debugLogIRs?.({
kind: 'debug',
@@ -162,13 +157,13 @@ function runWithEnvironment(
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
validateContextVariableLValues(hir);
validateUseMemo(hir).unwrap();
validateUseMemo(hir);
if (
env.isInferredMemoEnabled &&
!env.config.enablePreserveExistingManualUseMemo &&
!env.config.disableMemoizationForDebugging &&
!env.config.enableChangeDetectionForDebugging
!env.config.enableChangeDetectionForDebugging &&
!env.config.enableMinimalTransformsForRetry
) {
dropManualMemoization(hir);
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
@@ -201,13 +196,8 @@ function runWithEnvironment(
inferTypes(hir);
log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.isInferredMemoEnabled) {
if (env.config.validateHooksUsage) {
validateHooksUsage(hir).unwrap();
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir).unwrap();
}
if (env.config.validateHooksUsage) {
validateHooksUsage(hir);
}
if (env.config.enableFire) {
@@ -215,6 +205,10 @@ function runWithEnvironment(
log({kind: 'hir', name: 'TransformFire', value: hir});
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir);
}
if (env.config.lowerContextAccess) {
lowerContextAccess(hir, env.config.lowerContextAccess);
}
@@ -225,12 +219,7 @@ function runWithEnvironment(
analyseFunctions(hir);
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
const fnEffectErrors = inferReferenceEffects(hir);
if (env.isInferredMemoEnabled) {
if (fnEffectErrors.length > 0) {
CompilerError.throw(fnEffectErrors[0]);
}
}
inferReferenceEffects(hir);
log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
validateLocalsNotReassignedAfterRender(hir);
@@ -250,30 +239,28 @@ function runWithEnvironment(
inferMutableRanges(hir);
log({kind: 'hir', name: 'InferMutableRanges', value: hir});
if (env.isInferredMemoEnabled) {
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir).unwrap();
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir);
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir).unwrap();
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir);
}
if (env.config.validateNoSetStateInPassiveEffects) {
env.logErrors(validateNoSetStateInPassiveEffects(hir));
}
if (env.config.validateNoSetStateInPassiveEffects) {
validateNoSetStateInPassiveEffects(hir);
}
if (env.config.validateNoJSXInTryStatements) {
env.logErrors(validateNoJSXInTryStatement(hir));
}
if (env.config.validateNoJSXInTryStatements) {
validateNoJSXInTryStatement(hir);
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir).unwrap();
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir);
}
inferReactivePlaces(hir);
@@ -293,16 +280,7 @@ function runWithEnvironment(
value: hir,
});
if (env.isInferredMemoEnabled) {
if (env.config.validateStaticComponents) {
env.logErrors(validateStaticComponents(hir));
}
/**
* Only create reactive scopes (which directly map to generated memo blocks)
* if inferred memoization is enabled. This makes all later passes which
* transform reactive-scope labeled instructions no-ops.
*/
if (!env.config.enableMinimalTransformsForRetry) {
inferReactiveScopeVariables(hir);
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
}
@@ -514,14 +492,14 @@ function runWithEnvironment(
});
if (env.config.validateMemoizedEffectDependencies) {
validateMemoizedEffectDependencies(reactiveFunction).unwrap();
validateMemoizedEffectDependencies(reactiveFunction);
}
if (
env.config.enablePreserveExistingMemoizationGuarantees ||
env.config.validatePreserveExistingMemoizationGuarantees
) {
validatePreservedManualMemoization(reactiveFunction).unwrap();
validatePreservedManualMemoization(reactiveFunction);
}
const ast = codegenFunction(reactiveFunction, {
@@ -551,8 +529,7 @@ export function compileFn(
>,
config: EnvironmentConfig,
fnType: ReactFunctionType,
mode: CompilerMode,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
logger: Logger | null,
filename: string | null,
code: string | null,
@@ -561,8 +538,7 @@ export function compileFn(
func,
config,
fnType,
mode,
programContext,
useMemoCacheIdentifier,
logger,
filename,
code,

View File

@@ -12,7 +12,13 @@ import {
CompilerErrorDetail,
ErrorSeverity,
} from '../CompilerError';
import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment';
import {
EnvironmentConfig,
ExternalFunction,
ReactFunctionType,
MINIMAL_RETRY_CONFIG,
tryParseExternalFunction,
} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
import {isHookDeclaration} from '../Utils/HookDeclaration';
@@ -20,17 +26,16 @@ import {assertExhaustive} from '../Utils/utils';
import {insertGatedFunctionDeclaration} from './Gating';
import {
addImportsToProgram,
ProgramContext,
updateMemoCacheFunctionImport,
validateRestrictedImports,
} from './Imports';
import {CompilerReactTarget, PluginOptions} from './Options';
import {PluginOptions} from './Options';
import {compileFn} from './Pipeline';
import {
filterSuppressionsThatAffectFunction,
findProgramSuppressions,
suppressionsToCompilerError,
} from './Suppression';
import {GeneratedSource} from '../HIR';
export type CompilerPass = {
opts: PluginOptions;
@@ -268,10 +273,6 @@ function isFilePartOfSources(
return false;
}
export type CompileProgramResult = {
retryErrors: Array<{fn: BabelFn; error: CompilerError}>;
inferredEffectLocations: Set<t.SourceLocation>;
};
/**
* `compileProgram` is directly invoked by the react-compiler babel plugin, so
* exceptions thrown by this function will fail the babel build.
@@ -286,23 +287,19 @@ export type CompileProgramResult = {
export function compileProgram(
program: NodePath<t.Program>,
pass: CompilerPass,
): CompileProgramResult | null {
): void {
if (shouldSkipCompilation(program, pass)) {
return null;
return;
}
const environment = pass.opts.environment;
const restrictedImportsErr = validateRestrictedImports(program, environment);
if (restrictedImportsErr) {
handleError(restrictedImportsErr, pass, null);
return null;
return;
}
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
const programContext = new ProgramContext(
program,
pass.opts.target,
environment.hookPattern,
);
/*
* Record lint errors and critical errors as depending on Forget's config,
* we may still need to run Forget's analysis on every function (even if we
@@ -370,8 +367,7 @@ export function compileProgram(
filename: pass.filename ?? null,
},
);
const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
const inferredEffectLocations = new Set<t.SourceLocation>();
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType,
@@ -412,8 +408,29 @@ export function compileProgram(
fn,
environment,
fnType,
'all_features',
programContext,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
),
};
} catch (err) {
compileResult = {kind: 'error', error: err};
}
}
// If non-memoization features are enabled, retry regardless of error kind
if (compileResult.kind === 'error' && environment.enableFire) {
try {
compileResult = {
kind: 'compile',
compiledFn: compileFn(
fn,
{
...environment,
...MINIMAL_RETRY_CONFIG,
},
fnType,
useMemoCacheIdentifier.name,
pass.opts.logger,
pass.filename,
pass.code,
@@ -423,7 +440,6 @@ export function compileProgram(
compileResult = {kind: 'error', error: err};
}
}
if (compileResult.kind === 'error') {
/**
* If an opt out directive is present, log only instead of throwing and don't mark as
@@ -434,39 +450,7 @@ export function compileProgram(
} else {
handleError(compileResult.error, pass, fn.node.loc ?? null);
}
// If non-memoization features are enabled, retry regardless of error kind
if (
!(environment.enableFire || environment.inferEffectDependencies != null)
) {
return null;
}
try {
compileResult = {
kind: 'compile',
compiledFn: compileFn(
fn,
environment,
fnType,
'no_inferred_memo',
programContext,
pass.opts.logger,
pass.filename,
pass.code,
),
};
if (
!compileResult.compiledFn.hasFireRewrite &&
!compileResult.compiledFn.hasInferredEffect
) {
return null;
}
} catch (err) {
// TODO: we might want to log error here, but this will also result in duplicate logging
if (err instanceof CompilerError) {
retryErrors.push({fn, error: err});
}
return null;
}
return null;
}
pass.opts.logger?.logEvent(pass.filename, {
@@ -512,14 +496,6 @@ export function compileProgram(
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;
};
@@ -563,32 +539,81 @@ export function compileProgram(
program.node.directives,
);
if (moduleScopeOptOutDirectives.length > 0) {
return null;
return;
}
if (pass.opts.gating != null) {
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
program,
compiledFns.map(result => {
return result.originalFn;
}),
);
if (error) {
handleError(error, pass, null);
return;
}
}
const hasLoweredContextAccess = compiledFns.some(
c => c.compiledFn.hasLoweredContextAccess,
);
const externalFunctions: Array<ExternalFunction> = [];
let gating: null | ExternalFunction = null;
try {
// TODO: check for duplicate import specifiers
if (pass.opts.gating != null) {
gating = tryParseExternalFunction(pass.opts.gating);
externalFunctions.push(gating);
}
const lowerContextAccess = environment.lowerContextAccess;
if (lowerContextAccess && hasLoweredContextAccess) {
externalFunctions.push(lowerContextAccess);
}
const enableEmitInstrumentForget = environment.enableEmitInstrumentForget;
if (enableEmitInstrumentForget != null) {
externalFunctions.push(enableEmitInstrumentForget.fn);
if (enableEmitInstrumentForget.gating != null) {
externalFunctions.push(enableEmitInstrumentForget.gating);
}
}
if (environment.enableEmitFreeze != null) {
externalFunctions.push(environment.enableEmitFreeze);
}
if (environment.enableEmitHookGuards != null) {
externalFunctions.push(environment.enableEmitHookGuards);
}
if (environment.enableChangeDetectionForDebugging != null) {
externalFunctions.push(environment.enableChangeDetectionForDebugging);
}
const hasFireRewrite = compiledFns.some(c => c.compiledFn.hasFireRewrite);
if (environment.enableFire && hasFireRewrite) {
externalFunctions.push({
source: getReactCompilerRuntimeModule(pass.opts),
importSpecifierName: 'useFire',
});
}
} catch (err) {
handleError(err, pass, null);
return;
}
/*
* Only insert Forget-ified functions if we have not encountered a critical
* error elsewhere in the file, regardless of bailout mode.
*/
const referencedBeforeDeclared =
pass.opts.gating != null
? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns)
: null;
for (const result of compiledFns) {
const {kind, originalFn, compiledFn} = result;
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
if (referencedBeforeDeclared != null && kind === 'original') {
CompilerError.invariant(pass.opts.gating != null, {
reason: "Expected 'gating' import to be present",
loc: null,
});
insertGatedFunctionDeclaration(
originalFn,
transformedFn,
programContext,
pass.opts.gating,
referencedBeforeDeclared.has(result),
);
if (gating != null && kind === 'original') {
insertGatedFunctionDeclaration(originalFn, transformedFn, gating);
} else {
originalFn.replaceWith(transformedFn);
}
@@ -596,9 +621,23 @@ export function compileProgram(
// Forget compiled the component, we need to update existing imports of useMemoCache
if (compiledFns.length > 0) {
addImportsToProgram(program, programContext);
let needsMemoCacheFunctionImport = false;
for (const fn of compiledFns) {
if (fn.compiledFn.memoSlotsUsed > 0) {
needsMemoCacheFunctionImport = true;
break;
}
}
if (needsMemoCacheFunctionImport) {
updateMemoCacheFunctionImport(
program,
getReactCompilerRuntimeModule(pass.opts),
useMemoCacheIdentifier.name,
);
}
addImportsToProgram(program, externalFunctions);
}
return {retryErrors, inferredEffectLocations};
}
function shouldSkipCompilation(
@@ -629,7 +668,7 @@ function shouldSkipCompilation(
if (
hasMemoCacheFunctionImport(
program,
getReactCompilerRuntimeModule(pass.opts.target),
getReactCompilerRuntimeModule(pass.opts),
)
) {
return true;
@@ -954,39 +993,31 @@ function callsHooksOrCreatesJsx(
return invokesHooks || createsJsx;
}
function isNonNode(node?: t.Expression | null): boolean {
if (!node) {
return true;
}
switch (node.type) {
case 'ObjectExpression':
case 'ArrowFunctionExpression':
case 'FunctionExpression':
case 'BigIntLiteral':
case 'ClassExpression':
case 'NewExpression': // technically `new Array()` is legit, but unlikely
return true;
}
return false;
}
function returnsNonNode(
node: NodePath<
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
): boolean {
let hasReturn = false;
let returnsNonNode = false;
if (
// node.traverse#ArrowFunctionExpression isn't called for the root node
node.type === 'ArrowFunctionExpression' &&
node.node.body.type !== 'BlockStatement'
) {
returnsNonNode = isNonNode(node.node.body);
}
node.traverse({
ReturnStatement(ret) {
returnsNonNode = isNonNode(ret.node.argument);
hasReturn = true;
const argument = ret.node.argument;
if (argument == null) {
returnsNonNode = true;
} else {
switch (argument.type) {
case 'ObjectExpression':
case 'ArrowFunctionExpression':
case 'FunctionExpression':
case 'BigIntLiteral':
case 'ClassExpression':
case 'NewExpression': // technically `new Array()` is legit, but unlikely
returnsNonNode = true;
}
}
},
// Skip traversing all nested functions and their return statements
ArrowFunctionExpression: skipNestedFunctions(node),
@@ -995,7 +1026,7 @@ function returnsNonNode(
ObjectMethod: node => node.skip(),
});
return returnsNonNode;
return !hasReturn || returnsNonNode;
}
/*
@@ -1062,23 +1093,20 @@ function getFunctionName(
}
}
function getFunctionReferencedBeforeDeclarationAtTopLevel(
function checkFunctionReferencedBeforeDeclarationAtTopLevel(
program: NodePath<t.Program>,
fns: Array<CompileResult>,
): Set<CompileResult> {
const fnNames = new Map<string, {id: t.Identifier; fn: CompileResult}>(
fns: Array<BabelFn>,
): CompilerError | null {
const fnIds = new Set(
fns
.map<[NodePath<t.Expression> | null, CompileResult]>(fn => [
getFunctionName(fn.originalFn),
fn,
])
.map(fn => getFunctionName(fn))
.filter(
(entry): entry is [NodePath<t.Identifier>, CompileResult] =>
!!entry[0] && entry[0].isIdentifier(),
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier(),
)
.map(entry => [entry[0].node.name, {id: entry[0].node, fn: entry[1]}]),
.map(name => name.node),
);
const referencedBeforeDeclaration = new Set<CompileResult>();
const fnNames = new Map([...fnIds].map(id => [id.name, id]));
const errors = new CompilerError();
program.traverse({
TypeAnnotation(path) {
@@ -1104,7 +1132,8 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
* We've reached the declaration, hoisting is no longer possible, stop
* checking for this component name.
*/
if (id.node === fn.id) {
if (fnIds.has(id.node)) {
fnIds.delete(id.node);
fnNames.delete(id.node.name);
return;
}
@@ -1114,27 +1143,33 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel(
* A null scope means there's no function scope, which means we're at the
* top level scope.
*/
if (scope === null && id.isReferencedIdentifier()) {
referencedBeforeDeclaration.add(fn.fn);
if (scope === null) {
errors.pushErrorDetail(
new CompilerErrorDetail({
reason: `Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting`,
description: `Rewrite the reference to ${fn.name} to not rely on hoisting to fix this issue`,
loc: fn.loc ?? null,
suggestions: null,
severity: ErrorSeverity.Invariant,
}),
);
}
},
});
return referencedBeforeDeclaration;
return errors.details.length > 0 ? errors : null;
}
export function getReactCompilerRuntimeModule(
target: CompilerReactTarget,
): string {
if (target === '19') {
function getReactCompilerRuntimeModule(opts: PluginOptions): string {
if (opts.target === '19') {
return 'react/compiler-runtime'; // from react namespace
} else if (target === '17' || target === '18') {
} else if (opts.target === '17' || opts.target === '18') {
return 'react-compiler-runtime'; // npm package
} else {
CompilerError.invariant(
target != null &&
target.kind === 'donotuse_meta_internal' &&
typeof target.runtimeModule === 'string',
opts.target != null &&
opts.target.kind === 'donotuse_meta_internal' &&
typeof opts.target.runtimeModule === 'string',
{
reason: 'Expected target to already be validated',
description: null,
@@ -1142,6 +1177,6 @@ export function getReactCompilerRuntimeModule(
suggestions: null,
},
);
return target.runtimeModule;
return opts.target.runtimeModule;
}
}

View File

@@ -3,9 +3,6 @@ import {hasOwnProperty} from '../Utils/utils';
import {PluginOptions} from './Options';
function hasModule(name: string): boolean {
if (typeof require === 'undefined') {
return false;
}
try {
return !!require.resolve(name);
} catch (error: any) {

View File

@@ -1,283 +0,0 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetailOptions,
EnvironmentConfig,
ErrorSeverity,
Logger,
} from '..';
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'>,
{logger, filename}: TraversalState,
): never {
const detail: CompilerErrorDetailOptions = {
...options,
severity: ErrorSeverity.InvalidReact,
};
logger?.logEvent(filename, {
kind: 'CompileError',
fnLoc: null,
detail,
});
CompilerError.throw(detail);
}
function assertValidEffectImportReference(
numArgs: number,
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
for (const path of paths) {
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) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
path,
context.transformErrors,
);
/**
* Note that we cannot easily check the type of the first argument here,
* as it may have already been transformed by the compiler (and not
* memoized).
*/
throwInvalidReact(
{
reason:
'[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.',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: parent.node.loc ?? null,
},
context,
);
}
}
}
}
function assertValidFireImportReference(
paths: Array<NodePath<t.Node>>,
context: TraversalState,
): void {
if (paths.length > 0) {
const maybeErrorDiagnostic = matchCompilerDiagnostic(
paths[0],
context.transformErrors,
);
throwInvalidReact(
{
reason:
'[Fire] Untransformed reference to compiler-required feature. ' +
'Either remove this `fire` call or ensure it is successfully transformed by the compiler',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: paths[0].node.loc ?? null,
},
context,
);
}
}
export default function validateNoUntransformedReferences(
path: NodePath<t.Program>,
filename: string | null,
logger: Logger | null,
env: EnvironmentConfig,
compileResult: CompileProgramResult | null,
): void {
const moduleLoadChecks = new Map<
string,
Map<string, CheckInvalidReferenceFn>
>();
if (env.enableFire) {
/**
* Error on any untransformed references to `fire` (e.g. including non-call
* expressions)
*/
for (const module of Environment.knownReactModules) {
const react = getOrInsertWith(moduleLoadChecks, module, () => new Map());
react.set('fire', assertValidFireImportReference);
}
}
if (env.inferEffectDependencies) {
for (const {
function: {source, importSpecifierName},
numRequiredArgs,
} of env.inferEffectDependencies) {
const module = getOrInsertWith(moduleLoadChecks, source, () => new Map());
module.set(
importSpecifierName,
assertValidEffectImportReference.bind(null, numRequiredArgs),
);
}
}
if (moduleLoadChecks.size > 0) {
transformProgram(path, moduleLoadChecks, filename, logger, compileResult);
}
}
type TraversalState = {
shouldInvalidateScopes: boolean;
program: NodePath<t.Program>;
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>>,
context: TraversalState,
) => void;
function validateImportSpecifier(
specifier: NodePath<t.ImportSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
const imported = specifier.get('imported');
const specifierName: string =
imported.node.type === 'Identifier'
? imported.node.name
: imported.node.value;
const checkFn = importSpecifierChecks.get(specifierName);
if (checkFn == null) {
return;
}
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? null,
});
checkFn(binding.referencePaths, state);
}
function validateNamespacedImport(
specifier: NodePath<t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier>,
importSpecifierChecks: Map<string, CheckInvalidReferenceFn>,
state: TraversalState,
): void {
if (state.shouldInvalidateScopes) {
state.shouldInvalidateScopes = false;
state.program.scope.crawl();
}
const local = specifier.get('local');
const binding = local.scope.getBinding(local.node.name);
const defaultCheckFn = importSpecifierChecks.get(DEFAULT_EXPORT);
CompilerError.invariant(binding != null, {
reason: 'Expected binding to be found for import specifier',
loc: local.node.loc ?? null,
});
const filteredReferences = new Map<
CheckInvalidReferenceFn,
Array<NodePath<t.Node>>
>();
for (const reference of binding.referencePaths) {
if (defaultCheckFn != null) {
getOrInsertWith(filteredReferences, defaultCheckFn, () => []).push(
reference,
);
}
const parent = reference.parentPath;
if (
parent != null &&
parent.isMemberExpression() &&
parent.get('object') === reference
) {
if (parent.node.computed || parent.node.property.type !== 'Identifier') {
continue;
}
const checkFn = importSpecifierChecks.get(parent.node.property.name);
if (checkFn != null) {
getOrInsertWith(filteredReferences, checkFn, () => []).push(parent);
}
}
}
for (const [checkFn, references] of filteredReferences) {
checkFn(references, state);
}
}
function transformProgram(
path: NodePath<t.Program>,
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
filename: string | null,
logger: Logger | null,
compileResult: CompileProgramResult | null,
): void {
const traversalState: TraversalState = {
shouldInvalidateScopes: true,
program: path,
filename,
logger,
transformErrors: compileResult?.retryErrors ?? [],
inferredEffectLocations:
compileResult?.inferredEffectLocations ?? new Set(),
};
path.traverse({
ImportDeclaration(path: NodePath<t.ImportDeclaration>) {
const importSpecifierChecks = moduleLoadChecks.get(
path.node.source.value,
);
if (importSpecifierChecks == null) {
return;
}
const specifiers = path.get('specifiers');
for (const specifier of specifiers) {
if (specifier.isImportSpecifier()) {
validateImportSpecifier(
specifier,
importSpecifierChecks,
traversalState,
);
} else {
validateNamespacedImport(
specifier as NodePath<
t.ImportNamespaceSpecifier | t.ImportDefaultSpecifier
>,
importSpecifierChecks,
traversalState,
);
}
}
},
});
}
function matchCompilerDiagnostic(
badReference: NodePath<t.Node>,
transformErrors: Array<{fn: NodePath<t.Node>; error: CompilerError}>,
): string | null {
for (const {fn, error} of transformErrors) {
if (fn.isAncestor(badReference)) {
return error.toString();
}
}
return null;
}

View File

@@ -1455,11 +1455,6 @@ function lowerObjectPropertyKey(
kind: 'identifier',
name: key.node.name,
};
} else if (key.isNumericLiteral()) {
return {
kind: 'identifier',
name: String(key.node.value),
};
}
builder.errors.push({
@@ -1914,31 +1909,16 @@ function lowerExpression(
if (operator === '=') {
const left = expr.get('left');
if (left.isLVal()) {
return lowerAssignment(
builder,
left.node.loc ?? GeneratedSource,
InstructionKind.Reassign,
left,
lowerExpressionToTemporary(builder, expr.get('right')),
left.isArrayPattern() || left.isObjectPattern()
? 'Destructure'
: 'Assignment',
);
} else {
/**
* OptionalMemberExpressions as the left side of an AssignmentExpression are Stage 1 and
* not supported by React Compiler yet.
*/
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Unsupported syntax on the left side of an AssignmentExpression`,
description: `Expected an LVal, got: ${left.type}`,
severity: ErrorSeverity.Todo,
loc: left.node.loc ?? null,
suggestions: null,
});
return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};
}
return lowerAssignment(
builder,
left.node.loc ?? GeneratedSource,
InstructionKind.Reassign,
left,
lowerExpressionToTemporary(builder, expr.get('right')),
left.isArrayPattern() || left.isObjectPattern()
? 'Destructure'
: 'Assignment',
);
}
const operators: {
@@ -2111,7 +2091,7 @@ function lowerExpression(
propName = namePath.node.name;
if (propName.indexOf(':') !== -1) {
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name \`${propName}\``,
reason: `(BuildHIR::lowerExpression) Unexpected colon in attribute name \`${name}\``,
severity: ErrorSeverity.Todo,
loc: namePath.node.loc ?? null,
suggestions: null,

View File

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

View File

@@ -11,12 +11,10 @@ import {fromZodError} from 'zod-validation-error';
import {CompilerError} from '../CompilerError';
import {
CompilationMode,
defaultOptions,
Logger,
PanicThresholdOptions,
parsePluginOptions,
PluginOptions,
ProgramContext,
} from '../Entrypoint';
import {Err, Ok, Result} from '../Utils/Result';
import {
@@ -86,8 +84,6 @@ export const InstrumentationSchema = z
);
export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
export const USE_FIRE_FUNCTION_NAME = 'useFire';
export const EMIT_FREEZE_GLOBAL_GATING = '__DEV__';
export const MacroMethodSchema = z.union([
z.object({type: z.literal('wildcard')}),
@@ -100,8 +96,6 @@ export const MacroSchema = z.union([
z.tuple([z.string(), z.array(MacroMethodSchema)]),
]);
export type CompilerMode = 'all_features' | 'no_inferred_memo';
export type Macro = z.infer<typeof MacroSchema>;
export type MacroMethod = z.infer<typeof MacroMethodSchema>;
@@ -334,11 +328,6 @@ const EnvironmentConfigSchema = z.object({
*/
validateNoJSXInTryStatements: z.boolean().default(false),
/**
* Validates against dynamically creating components during render.
*/
validateStaticComponents: z.boolean().default(false),
/**
* Validates that the dependencies of all effect hooks are memoized. This helps ensure
* that Forget does not introduce infinite renders caused by a dependency changing,
@@ -561,6 +550,8 @@ const EnvironmentConfigSchema = z.object({
*/
disableMemoizationForDebugging: z.boolean().default(false),
enableMinimalTransformsForRetry: z.boolean().default(false),
/**
* When true, rather using memoized values, the compiler will always re-compute
* values, and then use a heuristic to compare the memoized value to the newly
@@ -635,6 +626,17 @@ const EnvironmentConfigSchema = z.object({
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
export const MINIMAL_RETRY_CONFIG: PartialEnvironmentConfig = {
validateHooksUsage: false,
validateRefAccessDuringRender: false,
validateNoSetStateInRender: false,
validateNoSetStateInPassiveEffects: false,
validateNoJSXInTryStatements: false,
validateMemoizedEffectDependencies: false,
validateNoCapitalizedCalls: null,
validateBlocklistedImports: null,
enableMinimalTransformsForRetry: true,
};
/**
* For test fixtures and playground only.
*
@@ -661,7 +663,7 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
source: 'react-compiler-runtime',
importSpecifierName: 'shouldInstrument',
},
globalGating: 'DEV',
globalGating: '__DEV__',
},
enableEmitHookGuards: {
source: 'react-compiler-runtime',
@@ -780,7 +782,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 +807,12 @@ export function parseConfigPragmaForTests(
panicThreshold = 'none';
break;
}
case '@noEmit': {
noEmit = true;
break;
}
}
}
return parsePluginOptions({
environment,
compilationMode,
panicThreshold,
noEmit,
});
}
@@ -855,11 +851,9 @@ export class Environment {
code: string | null;
config: EnvironmentConfig;
fnType: ReactFunctionType;
compilerMode: CompilerMode;
programContext: ProgramContext;
useMemoCacheIdentifier: string;
hasLoweredContextAccess: boolean;
hasFireRewrite: boolean;
hasInferredEffect: boolean;
inferredEffectLocations: Set<SourceLocation> = new Set();
#contextIdentifiers: Set<t.Identifier>;
#hoistedIdentifiers: Set<t.Identifier>;
@@ -867,26 +861,24 @@ export class Environment {
constructor(
scope: BabelScope,
fnType: ReactFunctionType,
compilerMode: CompilerMode,
config: EnvironmentConfig,
contextIdentifiers: Set<t.Identifier>,
logger: Logger | null,
filename: string | null,
code: string | null,
programContext: ProgramContext,
useMemoCacheIdentifier: string,
) {
this.#scope = scope;
this.fnType = fnType;
this.compilerMode = compilerMode;
this.config = config;
this.filename = filename;
this.code = code;
this.logger = logger;
this.programContext = programContext;
this.useMemoCacheIdentifier = useMemoCacheIdentifier;
this.#shapes = new Map(DEFAULT_SHAPES);
this.#globals = new Map(DEFAULT_GLOBALS);
this.hasLoweredContextAccess = false;
this.hasFireRewrite = false;
this.hasInferredEffect = false;
if (
config.disableMemoizationForDebugging &&
@@ -932,10 +924,6 @@ export class Environment {
this.#hoistedIdentifiers = new Set();
}
get isInferredMemoEnabled(): boolean {
return this.compilerMode !== 'no_inferred_memo';
}
get nextIdentifierId(): IdentifierId {
return makeIdentifierId(this.#nextIdentifer++);
}
@@ -948,23 +936,6 @@ export class Environment {
return makeScopeId(this.#nextScope++);
}
get scope(): BabelScope {
return this.#scope;
}
logErrors(errors: Result<void, CompilerError>): void {
if (errors.isOk() || this.logger == null) {
return;
}
for (const error of errors.unwrapErr().details) {
this.logger.logEvent(this.filename, {
kind: 'CompileError',
detail: error,
fnLoc: null,
});
}
}
isContextIdentifier(node: t.Identifier): boolean {
return this.#contextIdentifiers.has(node);
}
@@ -1154,34 +1125,10 @@ export class Environment {
moduleName.toLowerCase() === 'react-dom'
);
}
static knownReactModules: ReadonlyArray<string> = ['react', 'react-dom'];
getFallthroughPropertyType(
receiver: Type,
_property: Type,
): BuiltInType | PolyType | null {
let shapeId = null;
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
shapeId = receiver.shapeId;
}
if (shapeId !== null) {
const shape = this.#shapes.get(shapeId);
CompilerError.invariant(shape !== undefined, {
reason: `[HIR] Forget internal error: cannot resolve shape ${shapeId}`,
description: null,
loc: null,
suggestions: null,
});
return shape.properties.get('*') ?? null;
}
return null;
}
getPropertyType(
receiver: Type,
property: string | number,
property: string,
): BuiltInType | PolyType | null {
let shapeId = null;
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
@@ -1199,19 +1146,17 @@ export class Environment {
loc: null,
suggestions: null,
});
if (typeof property === 'string') {
return (
shape.properties.get(property) ??
shape.properties.get('*') ??
(isHookName(property) ? this.#getCustomHookType() : null)
);
} else {
return shape.properties.get('*') ?? null;
let value =
shape.properties.get(property) ?? shape.properties.get('*') ?? null;
if (value === null && isHookName(property)) {
value = this.#getCustomHookType();
}
} else if (typeof property === 'string' && isHookName(property)) {
return value;
} else if (isHookName(property)) {
return this.#getCustomHookType();
} else {
return null;
}
return null;
}
getFunctionSignature(type: FunctionType): FunctionSignature | null {

View File

@@ -63,20 +63,8 @@ export function findContextIdentifiers(
state: FindContextIdentifierState,
): void {
const left = path.get('left');
if (left.isLVal()) {
const currentFn = state.currentFn.at(-1) ?? null;
handleAssignment(currentFn, state.identifiers, left);
} else {
/**
* OptionalMemberExpressions as the left side of an AssignmentExpression are Stage 1 and
* not supported by React Compiler yet.
*/
CompilerError.throwTodo({
reason: `Unsupported syntax on the left side of an AssignmentExpression`,
description: `Expected an LVal, got: ${left.type}`,
loc: left.node.loc ?? null,
});
}
const currentFn = state.currentFn.at(-1) ?? null;
handleAssignment(currentFn, state.identifiers, left);
},
UpdateExpression(
path: NodePath<t.UpdateExpression>,

View File

@@ -10,10 +10,7 @@ import {
BUILTIN_SHAPES,
BuiltInArrayId,
BuiltInFireId,
BuiltInMapId,
BuiltInMixedReadonlyId,
BuiltInObjectId,
BuiltInSetId,
BuiltInUseActionStateId,
BuiltInUseContextHookId,
BuiltInUseEffectHookId,
@@ -48,23 +45,29 @@ export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
// Hack until we add ObjectShapes for all globals
const UNTYPED_GLOBALS: Set<string> = new Set([
'String',
'Object',
'Function',
'Number',
'RegExp',
'Date',
'Error',
'Function',
'TypeError',
'RangeError',
'ReferenceError',
'SyntaxError',
'URIError',
'EvalError',
'Boolean',
'DataView',
'Float32Array',
'Float64Array',
'Int8Array',
'Int16Array',
'Int32Array',
'Map',
'Set',
'WeakMap',
'Uint8Array',
'Uint8ClampedArray',
@@ -72,8 +75,16 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
'Uint32Array',
'ArrayBuffer',
'JSON',
'parseFloat',
'parseInt',
'console',
'isNaN',
'eval',
'isFinite',
'encodeURI',
'decodeURI',
'encodeURIComponent',
'decodeURIComponent',
]);
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
@@ -90,23 +101,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* Object.fromEntries(iterable)
* iterable: An iterable, such as an Array or Map, containing a list of
* objects. Each object should have two properties.
* Returns a new object whose properties are given by the entries of the
* iterable.
*/
'fromEntries',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [Effect.ConditionallyMutate],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
]),
],
[
@@ -125,8 +119,8 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
],
/*
* https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.from
* Array.from(arrayLike, optionalFn, optionalThis)
* Note that the Effect of `arrayLike` is polymorphic i.e.
* Array.from(arrayLike, optionalFn, optionalThis) not added because
* the Effect of `arrayLike` is polymorphic i.e.
* - Effect.read if
* - it does not have an @iterator property and is array-like
* (i.e. has a length property)
@@ -134,20 +128,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
* - Effect.mutate if it is a self-mutative iterator (e.g. a generator
* function)
*/
[
'from',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [
Effect.ConditionallyMutateIterator,
Effect.ConditionallyMutate,
Effect.ConditionallyMutate,
],
restParam: Effect.Read,
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
}),
],
[
'of',
// Array.of(element0, ..., elementN)
@@ -378,118 +358,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
returnValueKind: ValueKind.Primitive,
}),
],
[
'parseInt',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'parseFloat',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'isNaN',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'isFinite',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'encodeURI',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'encodeURIComponent',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'decodeURI',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'decodeURIComponent',
addFunction(DEFAULT_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'Map',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
[
'Set',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
// TODO: rest of Global objects
];

View File

@@ -10,7 +10,7 @@ import * as t from '@babel/types';
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
import {Environment, ReactFunctionType} from './Environment';
import type {HookKind} from './ObjectShape';
import {HookKind} from './ObjectShape';
import {Type, makeType} from './Types';
import {z} from 'zod';
@@ -703,10 +703,6 @@ export type ObjectPropertyKey =
| {
kind: 'computed';
name: Place;
}
| {
kind: 'number';
name: number;
};
export type ObjectProperty = {
@@ -829,13 +825,6 @@ export type CallExpression = {
typeArguments?: Array<t.FlowType>;
};
export type NewExpression = {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
};
export type LoadLocal = {
kind: 'LoadLocal';
place: Place;
@@ -901,7 +890,12 @@ export type InstructionValue =
right: Place;
loc: SourceLocation;
}
| NewExpression
| {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
}
| CallExpression
| MethodCall
| {
@@ -1167,21 +1161,18 @@ export type VariableBinding =
// bindings declard outside the current component/hook
| NonLocalBinding;
// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
export type NonLocalImportSpecifier = {
kind: 'ImportSpecifier';
name: string;
module: string;
imported: string;
};
export type NonLocalBinding =
// `import Foo from 'foo'`: name=Foo, module=foo
| {kind: 'ImportDefault'; name: string; module: string}
// `import * as Foo from 'foo'`: name=Foo, module=foo
| {kind: 'ImportNamespace'; name: string; module: string}
// `import {bar as baz} from 'foo'`
| NonLocalImportSpecifier
// `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
| {
kind: 'ImportSpecifier';
name: string;
module: string;
imported: string;
}
// let, const, function, etc declared in the module but outside the current component/hook
| {kind: 'ModuleLocal'; name: string}
// an unresolved binding
@@ -1399,7 +1390,6 @@ export enum Effect {
Read = 'read',
// This reference reads and stores the value
Capture = 'capture',
ConditionallyMutateIterator = 'mutate-iterator?',
/*
* This reference *may* write to (mutate) the value. This covers two similar cases:
* - The compiler is being conservative and assuming that a value *may* be mutated
@@ -1418,11 +1408,11 @@ export enum Effect {
// This reference may alias to (mutate) the value
Store = 'store',
}
export const EffectSchema = z.enum([
Effect.Read,
Effect.Mutate,
Effect.ConditionallyMutate,
Effect.ConditionallyMutateIterator,
Effect.Capture,
Effect.Store,
Effect.Freeze,
@@ -1436,7 +1426,6 @@ export function isMutableEffect(
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
return true;
}
@@ -1656,14 +1645,6 @@ export function isArrayType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
}
export function isMapType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInMap';
}
export function isSetType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInSet';
}
export function isPropsType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
}

View File

@@ -331,7 +331,6 @@ export default class HIRBuilder {
type: makeType(),
loc: node.loc ?? GeneratedSource,
};
this.#env.programContext.addNewReference(name);
this.#bindings.set(name, {node, identifier});
return identifier;
} else if (mapping.node === node) {

View File

@@ -44,7 +44,6 @@ export function addFunction(
properties: Iterable<[string, BuiltInType | PolyType]>,
fn: Omit<FunctionSignature, 'hookKind'>,
id: string | null = null,
isConstructor: boolean = false,
): FunctionType {
const shapeId = id ?? createAnonId();
addShape(registry, shapeId, properties, {
@@ -55,7 +54,6 @@ export function addFunction(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor,
};
}
@@ -75,7 +73,6 @@ export function addHook(
kind: 'Function',
return: fn.returnType,
shapeId,
isConstructor: false,
};
}
@@ -201,8 +198,6 @@ export type ObjectShape = {
export type ShapeRegistry = Map<string, ObjectShape>;
export const BuiltInPropsId = 'BuiltInProps';
export const BuiltInArrayId = 'BuiltInArray';
export const BuiltInSetId = 'BuiltInSet';
export const BuiltInMapId = 'BuiltInMap';
export const BuiltInFunctionId = 'BuiltInFunction';
export const BuiltInJsxId = 'BuiltInJsx';
export const BuiltInObjectId = 'BuiltInObject';
@@ -456,313 +451,6 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
*/
]);
/* Built-in Set shape */
addObject(BUILTIN_SHAPES, BuiltInSetId, [
[
/**
* add(value)
* Parameters
* value: the value of the element to add to the Set object.
* Returns the Set object with added value.
*/
'add',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Store,
// returnValueKind is technically dependent on the ValueKind of the set itself
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* setInstance.delete(value)
* Returns true if value was already in Set; otherwise false.
*/
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
['size', PRIMITIVE_TYPE],
[
/**
* difference(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in this set but not in the other set.
*/
'difference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* union(other)
* Parameters
* other: A Set object, or set-like object.
* Returns a new Set object containing elements in either this set or the other set.
*/
'union',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* symmetricalDifference(other)
* Parameters
* other: A Set object, or set-like object.
* A new Set object containing elements which are in either this set or the other set, but not in both.
*/
'symmetricalDifference',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInSetId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* isSubsetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in this set are also in the other set, and false otherwise.
*/
'isSubsetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* isSupersetOf(other)
* Parameters
* other: A Set object, or set-like object.
* Returns true if all elements in the other set are also in this set, and false otherwise.
*/
'isSupersetOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* forEach(callbackFn)
* forEach(callbackFn, thisArg)
*/
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInMapId, [
[
/**
* clear()
* Parameters none
* Returns undefined
*/
'clear',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'get',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* Params
* key: the key of the element to add to the Map object. The key may be
* any JavaScript type (any primitive value or any type of JavaScript
* object).
* value: the value of the element to add to the Map object.
* Returns the Map object.
*/
'set',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture, Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMapId},
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Mutable,
}),
],
['size', PRIMITIVE_TYPE],
[
'forEach',
addFunction(BUILTIN_SHAPES, [], {
/**
* see Array.map explanation for why arguments are marked `ConditionallyMutate`
*/
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Primitive,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),
],
/**
* Iterators
*/
[
'entries',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'keys',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'values',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
['0', {kind: 'Poly'}],
[
@@ -847,30 +535,6 @@ addObject(BUILTIN_SHAPES, BuiltInRefValueId, [
['*', {kind: 'Object', shapeId: BuiltInRefValueId}],
]);
/**
* MixedReadOnly =
* | primitive
* | simple objects (Record<string, MixedReadOnly>)
* | Array<MixedReadOnly>
*
* APIs such as Relay — but also Flux and other data stores — often return a
* union of types with some interesting properties in terms of analysis.
*
* Given this constraint, if data came from Relay, then we should be able to
* infer things like `data.items.map(): Array`. That may seem like a leap at
* first but remember, we assume you're not patching builtins. Thus the only way
* data.items.map can exist and be a function, given the above set of data types
* and builtin JS methods, is if `data.items` was an Array, and `data.items.map`
* is therefore calling Array.prototype.map. Then we know that function returns
* an Array as well. This relies on the fact that map() is being called, so if
* data.items was some other type it would error at runtime - so it's sound.
*
* Note that this shape is currently only used for hook return values, which
* means that it's safe to type aliasing method-call return kinds as `Frozen`.
*
* Also note that all newly created arrays from method-calls (e.g. `.map`)
* have the appropriate mutable `BuiltInArray` shape
*/
addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
[
'toString',
@@ -882,36 +546,6 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
returnValueKind: ValueKind.Primitive,
}),
],
[
'indexOf',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'includes',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'Primitive'},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
'at',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInMixedReadonlyId},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Frozen,
}),
],
[
'map',
addFunction(BUILTIN_SHAPES, [], {
@@ -1008,9 +642,9 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [],
restParam: Effect.ConditionallyMutate,
returnType: {kind: 'Object', shapeId: BuiltInMixedReadonlyId},
returnType: {kind: 'Poly'},
calleeEffect: Effect.ConditionallyMutate,
returnValueKind: ValueKind.Frozen,
returnValueKind: ValueKind.Mutable,
noAlias: true,
mutableOnlyIfOperandsAreMutable: true,
}),

View File

@@ -330,9 +330,6 @@ function printObjectPropertyKey(key: ObjectPropertyKey): string {
case 'computed': {
return `[${printPlace(key.name)}]`;
}
case 'number': {
return String(key.name);
}
}
}

View File

@@ -38,7 +38,6 @@ export type FunctionType = {
kind: 'Function';
shapeId: string | null;
return: Type;
isConstructor: boolean;
};
export type ObjectType = {
@@ -61,15 +60,7 @@ export type PropType = {
kind: 'Property';
objectType: Type;
objectName: string;
propertyName:
| {
kind: 'literal';
value: PropertyLiteral;
}
| {
kind: 'computed';
value: Type;
};
propertyName: PropertyLiteral;
};
export type ObjectMethod = {
@@ -112,7 +103,6 @@ export function duplicateType(type: Type): Type {
kind: 'Function',
return: duplicateType(type.return),
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'Object': {

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

@@ -217,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({
@@ -228,7 +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);
}
}
}
@@ -251,7 +249,6 @@ export function inferEffectDependencies(fn: HIRFunction): void {
// Renumber instructions and fix scope ranges
markInstructionIds(fn.body);
fixScopeAndIdentifierRanges(fn.body);
fn.env.hasInferredEffect = true;
}
}

View File

@@ -5,12 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerError,
CompilerErrorDetailOptions,
ErrorSeverity,
ValueKind,
} from '..';
import {CompilerError, ErrorSeverity, ValueKind} from '..';
import {
AbstractValue,
BasicBlock,
@@ -295,21 +290,21 @@ export function inferTerminalFunctionEffects(
return functionEffects;
}
export function transformFunctionEffectErrors(
export function raiseFunctionEffectErrors(
functionEffects: Array<FunctionEffect>,
): Array<CompilerErrorDetailOptions> {
return functionEffects.map(eff => {
): void {
functionEffects.forEach(eff => {
switch (eff.kind) {
case 'ReactMutation':
case 'GlobalMutation': {
return eff.error;
CompilerError.throw(eff.error);
}
case 'ContextMutation': {
return {
CompilerError.throw({
severity: ErrorSeverity.Invariant,
reason: `Unexpected ContextMutation in top-level function effects`,
loc: eff.loc,
};
});
}
default:
assertExhaustive(

View File

@@ -11,10 +11,7 @@ import {
Identifier,
InstructionId,
InstructionKind,
isArrayType,
isMapType,
isRefOrRefValue,
isSetType,
makeInstructionId,
Place,
} from '../HIR/HIR';
@@ -93,17 +90,6 @@ function inferPlace(
infer(place, instrId);
}
return;
case Effect.ConditionallyMutateIterator: {
const identifier = place.identifier;
if (
!isArrayType(identifier) &&
!isSetType(identifier) &&
!isMapType(identifier)
) {
infer(place, instrId);
}
return;
}
case Effect.ConditionallyMutate:
case Effect.Mutate: {
infer(place, instrId);

View File

@@ -230,7 +230,6 @@ export function inferReactivePlaces(fn: HIRFunction): void {
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
if (isMutable(instruction, operand)) {
reactiveIdentifiers.markReactive(operand);

View File

@@ -5,14 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {CompilerError} from '../CompilerError';
import {Environment} from '../HIR';
import {
AbstractValue,
BasicBlock,
BlockId,
CallExpression,
NewExpression,
Effect,
FunctionEffect,
GeneratedSource,
@@ -24,15 +23,12 @@ import {
Phi,
Place,
SpreadPattern,
TInstruction,
Type,
ValueKind,
ValueReason,
isArrayType,
isMapType,
isMutableEffect,
isObjectType,
isSetType,
} from '../HIR/HIR';
import {FunctionSignature} from '../HIR/ObjectShape';
import {
@@ -42,6 +38,7 @@ import {
printSourceLocation,
} from '../HIR/PrintHIR';
import {
eachCallArgument,
eachInstructionOperand,
eachInstructionValueOperand,
eachPatternOperand,
@@ -52,7 +49,7 @@ import {assertExhaustive} from '../Utils/utils';
import {
inferTerminalFunctionEffects,
inferInstructionFunctionEffects,
transformFunctionEffectErrors,
raiseFunctionEffectErrors,
} from './InferFunctionEffects';
const UndefinedValue: InstructionValue = {
@@ -106,7 +103,7 @@ const UndefinedValue: InstructionValue = {
export default function inferReferenceEffects(
fn: HIRFunction,
options: {isFunctionExpression: boolean} = {isFunctionExpression: false},
): Array<CompilerErrorDetailOptions> {
): void {
/*
* Initial state contains function params
* TODO: include module declarations here as well
@@ -244,9 +241,8 @@ export default function inferReferenceEffects(
if (options.isFunctionExpression) {
fn.effects = functionEffects;
return [];
} else {
return transformFunctionEffectErrors(functionEffects);
} else if (!fn.env.config.enableMinimalTransformsForRetry) {
raiseFunctionEffectErrors(functionEffects);
}
}
@@ -254,7 +250,7 @@ type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
// Maintains a mapping of top-level variables to the kind of value they hold
class InferenceState {
env: Environment;
#env: Environment;
// The kind of each value, based on its allocation site
#values: Map<InstructionValue, AbstractValue>;
@@ -270,7 +266,7 @@ class InferenceState {
values: Map<InstructionValue, AbstractValue>,
variables: Map<IdentifierId, Set<InstructionValue>>,
) {
this.env = env;
this.#env = env;
this.#values = values;
this.#variables = variables;
}
@@ -412,8 +408,8 @@ class InferenceState {
});
if (
value.kind === 'FunctionExpression' &&
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
this.env.config.enableTransitivelyFreezeFunctionExpressions)
(this.#env.config.enablePreserveExistingMemoizationGuarantees ||
this.#env.config.enableTransitivelyFreezeFunctionExpressions)
) {
for (const operand of value.loweredFunc.func.context) {
const operandValues = this.#variables.get(operand.identifier.id);
@@ -471,25 +467,6 @@ class InferenceState {
}
break;
}
case Effect.ConditionallyMutateIterator: {
if (
valueKind.kind === ValueKind.Mutable ||
valueKind.kind === ValueKind.Context
) {
if (
isArrayType(place.identifier) ||
isSetType(place.identifier) ||
isMapType(place.identifier)
) {
effect = Effect.Capture;
} else {
effect = Effect.ConditionallyMutate;
}
} else {
effect = Effect.Read;
}
break;
}
case Effect.Mutate: {
effect = Effect.Mutate;
break;
@@ -612,7 +589,7 @@ class InferenceState {
return null;
} else {
return new InferenceState(
this.env,
this.#env,
nextValues ?? new Map(this.#values),
nextVariables ?? new Map(this.#variables),
);
@@ -626,7 +603,7 @@ class InferenceState {
*/
clone(): InferenceState {
return new InferenceState(
this.env,
this.#env,
new Map(this.#values),
new Map(this.#variables),
);
@@ -895,41 +872,52 @@ function inferBlock(
reason: new Set([ValueReason.Other]),
context: new Set(),
};
for (const element of instrValue.elements) {
if (element.kind === 'Spread') {
state.referenceAndRecordEffects(
freezeActions,
element.place,
Effect.ConditionallyMutateIterator,
ValueReason.Other,
);
} else if (element.kind === 'Identifier') {
state.referenceAndRecordEffects(
freezeActions,
element,
Effect.Capture,
ValueReason.Other,
);
} else {
let _: 'Hole' = element.kind;
}
}
state.initialize(instrValue, valueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.Store;
continuation = {
kind: 'funeffects',
kind: 'initialize',
valueKind,
effect: {kind: Effect.Capture, reason: ValueReason.Other},
lvalueEffect: Effect.Store,
};
break;
}
case 'NewExpression': {
inferCallEffects(
state,
instr as TInstruction<NewExpression>,
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
const valueKind: AbstractValue = {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
state.referenceAndRecordEffects(
freezeActions,
getFunctionCallSignature(env, instrValue.callee.identifier.type),
instrValue.callee,
Effect.Read,
ValueReason.Other,
);
for (const operand of eachCallArgument(instrValue.args)) {
state.referenceAndRecordEffects(
freezeActions,
operand,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
state.initialize(instrValue, valueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1227,12 +1215,71 @@ function inferBlock(
break;
}
case 'CallExpression': {
inferCallEffects(
state,
instr as TInstruction<CallExpression>,
freezeActions,
getFunctionCallSignature(env, instrValue.callee.identifier.type),
const signature = getFunctionCallSignature(
env,
instrValue.callee.identifier.type,
);
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ??
ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
if (effects !== null) {
state.referenceAndRecordEffects(
freezeActions,
place,
effects[i],
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
instrValue.callee,
signature.calleeEffect,
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
instrValue.callee,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= instrValue.callee.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1250,12 +1297,108 @@ function inferBlock(
Effect.Read,
ValueReason.Other,
);
inferCallEffects(
state,
instr as TInstruction<MethodCall>,
freezeActions,
getFunctionCallSignature(env, instrValue.property.identifier.type),
const signature = getFunctionCallSignature(
env,
instrValue.property.identifier.type,
);
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([ValueReason.Other]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
if (
signature !== null &&
signature.mutableOnlyIfOperandsAreMutable &&
areArgumentsImmutableAndNonMutating(state, instrValue.args)
) {
/*
* None of the args are mutable or mutate their params, we can downgrade to
* treating as all reads (except that the receiver may be captured)
*/
for (const arg of instrValue.args) {
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.Read,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.Capture,
ValueReason.Other,
);
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect =
instrValue.receiver.effect === Effect.Capture
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
if (effects !== null) {
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
effects[i],
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= place.effect === Effect.Capture;
}
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
signature.calleeEffect,
ValueReason.Other,
);
} else {
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= instrValue.receiver.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
continuation = {kind: 'funeffects'};
break;
}
@@ -1662,13 +1805,7 @@ function inferBlock(
kind === ValueKind.Mutable || kind === ValueKind.Context;
let effect;
let valueKind: AbstractValue;
const iterator = instrValue.collection.identifier;
if (
!isMutable ||
isArrayType(iterator) ||
isMapType(iterator) ||
isSetType(iterator)
) {
if (!isMutable || isArrayType(instrValue.collection.identifier)) {
// Case 1, assume iterator is a separate mutable object
effect = {
kind: Effect.Read,
@@ -1709,7 +1846,7 @@ function inferBlock(
state.referenceAndRecordEffects(
freezeActions,
instrValue.iterator,
Effect.ConditionallyMutateIterator,
Effect.ConditionallyMutate,
ValueReason.Other,
);
/**
@@ -1838,7 +1975,7 @@ export function getFunctionCallSignature(
* @returns Inferred effects of function arguments, or null if inference fails.
*/
export function getFunctionEffects(
fn: MethodCall | CallExpression | NewExpression,
fn: MethodCall | CallExpression,
sig: FunctionSignature,
): Array<Effect> | null {
const results = [];
@@ -1867,33 +2004,6 @@ export function getFunctionEffects(
return results;
}
export function isKnownMutableEffect(effect: Effect): boolean {
switch (effect) {
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
return true;
}
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
description: null,
loc: GeneratedSource,
suggestions: null,
});
}
case Effect.Read:
case Effect.Capture:
case Effect.Freeze: {
return false;
}
default: {
assertExhaustive(effect, `Unexpected effect \`${effect}\``);
}
}
}
/**
* Returns true if all of the arguments are both non-mutable (immutable or frozen)
* _and_ are not functions which might mutate their arguments. Note that function
@@ -1905,20 +2015,10 @@ function areArgumentsImmutableAndNonMutating(
args: MethodCall['args'],
): boolean {
for (const arg of args) {
if (arg.kind === 'Identifier' && arg.identifier.type.kind === 'Function') {
const fnShape = state.env.getFunctionSignature(arg.identifier.type);
if (fnShape != null) {
return (
!fnShape.positionalParams.some(isKnownMutableEffect) &&
(fnShape.restParam == null ||
!isKnownMutableEffect(fnShape.restParam))
);
}
}
const place = arg.kind === 'Identifier' ? arg : arg.place;
const kind = state.kind(place).kind;
switch (kind) {
case ValueKind.Global:
case ValueKind.Primitive:
case ValueKind.Frozen: {
/*
@@ -1929,10 +2029,6 @@ function areArgumentsImmutableAndNonMutating(
break;
}
default: {
/**
* Globals, module locals, and other locally defined functions may
* mutate their arguments.
*/
return false;
}
}
@@ -1953,150 +2049,3 @@ function areArgumentsImmutableAndNonMutating(
}
return true;
}
function getArgumentEffect(
signatureEffect: Effect | null,
arg: Place | SpreadPattern,
): Effect {
if (signatureEffect != null) {
if (arg.kind === 'Identifier') {
return signatureEffect;
} else if (
signatureEffect === Effect.Mutate ||
signatureEffect === Effect.ConditionallyMutate
) {
return signatureEffect;
} else {
// see call-spread-argument-mutable-iterator test fixture
if (signatureEffect === Effect.Freeze) {
CompilerError.throwTodo({
reason: 'Support spread syntax for hook arguments',
loc: arg.place.loc,
});
}
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
return Effect.ConditionallyMutateIterator;
}
} else {
return Effect.ConditionallyMutate;
}
}
function inferCallEffects(
state: InferenceState,
instr:
| TInstruction<CallExpression>
| TInstruction<MethodCall>
| TInstruction<NewExpression>,
freezeActions: Array<FreezeAction>,
signature: FunctionSignature | null,
): void {
const instrValue = instr.value;
const returnValueKind: AbstractValue =
signature !== null
? {
kind: signature.returnValueKind,
reason: new Set([
signature.returnValueReason ?? ValueReason.KnownReturnSignature,
]),
context: new Set(),
}
: {
kind: ValueKind.Mutable,
reason: new Set([ValueReason.Other]),
context: new Set(),
};
if (
instrValue.kind === 'MethodCall' &&
signature !== null &&
signature.mutableOnlyIfOperandsAreMutable &&
areArgumentsImmutableAndNonMutating(state, instrValue.args)
) {
/*
* None of the args are mutable or mutate their params, we can downgrade to
* treating as all reads (except that the receiver may be captured)
*/
for (const arg of instrValue.args) {
const place = arg.kind === 'Identifier' ? arg : arg.place;
state.referenceAndRecordEffects(
freezeActions,
place,
Effect.Read,
ValueReason.Other,
);
}
state.referenceAndRecordEffects(
freezeActions,
instrValue.receiver,
Effect.Capture,
ValueReason.Other,
);
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect =
instrValue.receiver.effect === Effect.Capture
? Effect.Store
: Effect.ConditionallyMutate;
return;
}
const effects =
signature !== null ? getFunctionEffects(instrValue, signature) : null;
let hasCaptureArgument = false;
for (let i = 0; i < instrValue.args.length; i++) {
const arg = instrValue.args[i];
const place = arg.kind === 'Identifier' ? arg : arg.place;
/*
* If effects are inferred for an argument, we should fail invalid
* mutating effects
*/
state.referenceAndRecordEffects(
freezeActions,
place,
getArgumentEffect(effects != null ? effects[i] : null, arg),
ValueReason.Other,
);
hasCaptureArgument ||= place.effect === Effect.Capture;
}
const callee =
instrValue.kind === 'MethodCall' ? instrValue.receiver : instrValue.callee;
if (signature !== null) {
state.referenceAndRecordEffects(
freezeActions,
callee,
signature.calleeEffect,
ValueReason.Other,
);
} else {
/**
* For new expressions, we infer a `read` effect on the Class / Function type
* to avoid extending mutable ranges of locally created classes, e.g.
* ```js
* const MyClass = getClass();
* const value = new MyClass(val1, val2)
* ^ (read) ^ (conditionally mutate)
* ```
*
* Risks:
* Classes / functions created during render could technically capture and
* mutate their enclosing scope, which we currently do not detect.
*/
state.referenceAndRecordEffects(
freezeActions,
callee,
instrValue.kind === 'NewExpression'
? Effect.Read
: Effect.ConditionallyMutate,
ValueReason.Other,
);
}
hasCaptureArgument ||= callee.effect === Effect.Capture;
state.initialize(instrValue, returnValueKind);
state.define(instr.lvalue, instrValue);
instr.lvalue.effect = hasCaptureArgument
? Effect.Store
: Effect.ConditionallyMutate;
}

View File

@@ -18,7 +18,6 @@ import {
Instruction,
LoadGlobal,
LoadLocal,
NonLocalImportSpecifier,
Place,
PropertyLoad,
isUseContextHookType,
@@ -36,7 +35,7 @@ import {inferTypes} from '../TypeInference';
export function lowerContextAccess(
fn: HIRFunction,
loweredContextCalleeConfig: ExternalFunction,
loweredContextCallee: ExternalFunction,
): void {
const contextAccess: Map<IdentifierId, CallExpression> = new Map();
const contextKeys: Map<IdentifierId, Array<string>> = new Map();
@@ -80,8 +79,6 @@ export function lowerContextAccess(
}
}
let importLoweredContextCallee: NonLocalImportSpecifier | null = null;
if (contextAccess.size > 0 && contextKeys.size > 0) {
for (const [, block] of fn.body.blocks) {
let nextInstructions: Array<Instruction> | null = null;
@@ -94,13 +91,9 @@ export function lowerContextAccess(
isUseContextHookType(value.callee.identifier) &&
contextKeys.has(lvalue.identifier.id)
) {
importLoweredContextCallee ??=
fn.env.programContext.addImportSpecifier(
loweredContextCalleeConfig,
);
const loweredContextCalleeInstr = emitLoadLoweredContextCallee(
fn.env,
importLoweredContextCallee,
loweredContextCallee,
);
if (nextInstructions === null) {
@@ -129,16 +122,21 @@ export function lowerContextAccess(
}
markInstructionIds(fn.body);
inferTypes(fn);
fn.env.hasLoweredContextAccess = true;
}
}
function emitLoadLoweredContextCallee(
env: Environment,
importedLowerContextCallee: NonLocalImportSpecifier,
loweredContextCallee: ExternalFunction,
): Instruction {
const loadGlobal: LoadGlobal = {
kind: 'LoadGlobal',
binding: {...importedLowerContextCallee},
binding: {
kind: 'ImportNamespace',
module: loweredContextCallee.source,
name: loweredContextCallee.importSpecifierName,
},
loc: GeneratedSource,
};

View File

@@ -196,7 +196,7 @@ function process(
return null;
}
const props = collectProps(fn.env, jsx);
const props = collectProps(jsx);
if (!props) return null;
const outlinedTag = fn.env.generateGloballyUniqueIdentifierName(null).value;
@@ -217,7 +217,6 @@ type OutlinedJsxAttribute = {
};
function collectProps(
env: Environment,
instructions: Array<JsxInstruction>,
): Array<OutlinedJsxAttribute> | null {
let id = 1;
@@ -228,7 +227,6 @@ function collectProps(
newName = `${oldName}${id++}`;
}
seen.add(newName);
env.programContext.addNewReference(newName);
return newName;
}

View File

@@ -14,7 +14,7 @@ import {
renameVariables,
} from '.';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {Environment, ExternalFunction} from '../HIR';
import {Environment, EnvironmentConfig, ExternalFunction} from '../HIR';
import {
ArrayPattern,
BlockId,
@@ -52,8 +52,7 @@ import {assertExhaustive} from '../Utils/utils';
import {buildReactiveFunction} from './BuildReactiveFunction';
import {SINGLE_CHILD_FBT_TAGS} from './MemoizeFbtAndMacroOperandsInSameScope';
import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
import {EMIT_FREEZE_GLOBAL_GATING, ReactFunctionType} from '../HIR/Environment';
import {ProgramContext} from '../Entrypoint';
import {ReactFunctionType} from '../HIR/Environment';
export const MEMO_CACHE_SENTINEL = 'react.memo_cache_sentinel';
export const EARLY_RETURN_SENTINEL = 'react.early_return_sentinel';
@@ -101,10 +100,9 @@ export type CodegenFunction = {
}>;
/**
* This is true if the compiler has compiled inferred effect dependencies
* This is true if the compiler has the lowered useContext calls.
*/
hasInferredEffect: boolean;
inferredEffectLocations: Set<SourceLocation>;
hasLoweredContextAccess: boolean;
/**
* This is true if the compiler has compiled a fire to a useFire call
@@ -158,11 +156,10 @@ export function codegenFunction(
const compiled = compileResult.unwrap();
const hookGuard = fn.env.config.enableEmitHookGuards;
if (hookGuard != null && fn.env.isInferredMemoEnabled) {
if (hookGuard != null) {
compiled.body = t.blockStatement([
createHookGuard(
hookGuard,
fn.env.programContext,
compiled.body.body,
GuardKind.PushHookGuard,
GuardKind.PopHookGuard,
@@ -173,15 +170,13 @@ export function codegenFunction(
const cacheCount = compiled.memoSlotsUsed;
if (cacheCount !== 0) {
const preface: Array<t.Statement> = [];
const useMemoCacheIdentifier =
fn.env.programContext.addMemoCacheImport().name;
// The import declaration for `useMemoCache` is inserted in the Babel plugin
preface.push(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(cx.synthesizeName('$')),
t.callExpression(t.identifier(useMemoCacheIdentifier), [
t.callExpression(t.identifier(fn.env.useMemoCacheIdentifier), [
t.numericLiteral(cacheCount),
]),
),
@@ -255,63 +250,39 @@ export function codegenFunction(
}
const emitInstrumentForget = fn.env.config.enableEmitInstrumentForget;
if (
emitInstrumentForget != null &&
fn.id != null &&
fn.env.isInferredMemoEnabled
) {
if (emitInstrumentForget != null && fn.id != null) {
/*
* Technically, this is a conditional hook call. However, we expect
* __DEV__ and gating identifier to be runtime constants
*/
const gating =
emitInstrumentForget.gating != null
? t.identifier(
fn.env.programContext.addImportSpecifier(
emitInstrumentForget.gating,
).name,
)
: null;
const globalGating =
let gating: t.Expression;
if (
emitInstrumentForget.gating != null &&
emitInstrumentForget.globalGating != null
? t.identifier(emitInstrumentForget.globalGating)
: null;
if (emitInstrumentForget.globalGating != null) {
const assertResult = fn.env.programContext.assertGlobalBinding(
emitInstrumentForget.globalGating,
) {
gating = t.logicalExpression(
'&&',
t.identifier(emitInstrumentForget.globalGating),
t.identifier(emitInstrumentForget.gating.importSpecifierName),
);
if (assertResult.isErr()) {
return assertResult;
}
}
let ifTest: t.Expression;
if (gating != null && globalGating != null) {
ifTest = t.logicalExpression('&&', globalGating, gating);
} else if (gating != null) {
ifTest = gating;
} else if (emitInstrumentForget.gating != null) {
gating = t.identifier(emitInstrumentForget.gating.importSpecifierName);
} else {
CompilerError.invariant(globalGating != null, {
CompilerError.invariant(emitInstrumentForget.globalGating != null, {
reason:
'Bad config not caught! Expected at least one of gating or globalGating',
loc: null,
suggestions: null,
});
ifTest = globalGating;
gating = t.identifier(emitInstrumentForget.globalGating);
}
const instrumentFnIdentifier = fn.env.programContext.addImportSpecifier(
emitInstrumentForget.fn,
).name;
const test: t.IfStatement = t.ifStatement(
ifTest,
gating,
t.expressionStatement(
t.callExpression(t.identifier(instrumentFnIdentifier), [
t.stringLiteral(fn.id),
t.stringLiteral(fn.env.filename ?? ''),
]),
t.callExpression(
t.identifier(emitInstrumentForget.fn.importSpecifierName),
[t.stringLiteral(fn.id), t.stringLiteral(fn.env.filename ?? '')],
),
),
);
compiled.body.body.unshift(test);
@@ -388,9 +359,8 @@ function codegenReactiveFunction(
prunedMemoBlocks: countMemoBlockVisitor.prunedMemoBlocks,
prunedMemoValues: countMemoBlockVisitor.prunedMemoValues,
outlined: [],
hasLoweredContextAccess: fn.env.hasLoweredContextAccess,
hasFireRewrite: fn.env.hasFireRewrite,
hasInferredEffect: fn.env.hasInferredEffect,
inferredEffectLocations: fn.env.inferredEffectLocations,
});
}
@@ -578,19 +548,14 @@ function codegenBlockNoReset(
}
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier(
cx.env.config.enableEmitFreeze,
).name;
cx.env.programContext
.assertGlobalBinding(EMIT_FREEZE_GLOBAL_GATING, cx.env.scope)
.unwrap();
if (cx.env.config.enableEmitFreeze != null) {
// The import declaration for emitFreeze is inserted in the Babel plugin
return t.conditionalExpression(
t.identifier(EMIT_FREEZE_GLOBAL_GATING),
t.callExpression(t.identifier(emitFreezeIdentifier), [
value,
t.stringLiteral(cx.fnName),
]),
t.identifier('__DEV__'),
t.callExpression(
t.identifier(cx.env.config.enableEmitFreeze.importSpecifierName),
[value, t.stringLiteral(cx.fnName)],
),
value,
);
} else {
@@ -744,14 +709,16 @@ function codegenReactiveScope(
let computationBlock = codegenBlock(cx, block);
let memoStatement;
const detectionFunction = cx.env.config.enableChangeDetectionForDebugging;
if (detectionFunction != null && changeExpressions.length > 0) {
if (
cx.env.config.enableChangeDetectionForDebugging != null &&
changeExpressions.length > 0
) {
const loc =
typeof scope.loc === 'symbol'
? 'unknown location'
: `(${scope.loc.start.line}:${scope.loc.end.line})`;
const importedDetectionFunctionIdentifier =
cx.env.programContext.addImportSpecifier(detectionFunction).name;
const detectionFunction =
cx.env.config.enableChangeDetectionForDebugging.importSpecifierName;
const cacheLoadOldValueStatements: Array<t.Statement> = [];
const changeDetectionStatements: Array<t.Statement> = [];
const idempotenceDetectionStatements: Array<t.Statement> = [];
@@ -773,7 +740,7 @@ function codegenReactiveScope(
);
changeDetectionStatements.push(
t.expressionStatement(
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
t.callExpression(t.identifier(detectionFunction), [
t.identifier(loadName),
t.cloneNode(name, true),
t.stringLiteral(name.name),
@@ -785,7 +752,7 @@ function codegenReactiveScope(
);
idempotenceDetectionStatements.push(
t.expressionStatement(
t.callExpression(t.identifier(importedDetectionFunctionIdentifier), [
t.callExpression(t.identifier(detectionFunction), [
t.cloneNode(slot, true),
t.cloneNode(name, true),
t.stringLiteral(name.name),
@@ -1547,15 +1514,15 @@ const createStringLiteral = withLoc(t.stringLiteral);
function createHookGuard(
guard: ExternalFunction,
context: ProgramContext,
stmts: Array<t.Statement>,
before: GuardKind,
after: GuardKind,
): t.TryStatement {
const guardFnName = context.addImportSpecifier(guard).name;
function createHookGuardImpl(kind: number): t.ExpressionStatement {
return t.expressionStatement(
t.callExpression(t.identifier(guardFnName), [t.numericLiteral(kind)]),
t.callExpression(t.identifier(guard.importSpecifierName), [
t.numericLiteral(kind),
]),
);
}
@@ -1586,7 +1553,7 @@ function createHookGuard(
* ```
*/
function createCallExpression(
env: Environment,
config: EnvironmentConfig,
callee: t.Expression,
args: Array<t.Expression | t.SpreadElement>,
loc: SourceLocation | null,
@@ -1597,15 +1564,14 @@ function createCallExpression(
callExpr.loc = loc;
}
const hookGuard = env.config.enableEmitHookGuards;
if (hookGuard != null && isHook && env.isInferredMemoEnabled) {
const hookGuard = config.enableEmitHookGuards;
if (hookGuard != null && isHook) {
const iife = t.functionExpression(
null,
[],
t.blockStatement([
createHookGuard(
hookGuard,
env.programContext,
[t.returnStatement(callExpr)],
GuardKind.AllowHook,
GuardKind.DisallowHook,
@@ -1735,7 +1701,7 @@ function codegenInstructionValue(
const callee = codegenPlaceToExpression(cx, instrValue.callee);
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
value = createCallExpression(
cx.env,
cx.env.config,
callee,
args,
instrValue.loc,
@@ -1825,7 +1791,7 @@ function codegenInstructionValue(
);
const args = instrValue.args.map(arg => codegenArgument(cx, arg));
value = createCallExpression(
cx.env,
cx.env.config,
memberExpr,
args,
instrValue.loc,
@@ -2463,9 +2429,6 @@ function codegenObjectPropertyKey(
});
return expr;
}
case 'number': {
return t.numericLiteral(key.name);
}
}
}

View File

@@ -178,15 +178,11 @@ function mergeLocation(l: SourceLocation, r: SourceLocation): SourceLocation {
return l;
} else {
return {
filename: l.filename,
identifierName: l.identifierName,
start: {
index: Math.min(l.start.index, r.start.index),
line: Math.min(l.start.line, r.start.line),
column: Math.min(l.start.column, r.start.column),
},
end: {
index: Math.max(l.end.index, r.end.index),
line: Math.max(l.end.line, r.end.line),
column: Math.max(l.end.column, r.end.column),
},
@@ -206,7 +202,7 @@ export function inRange(
return id >= range.start && id < range.end;
}
function mayAllocate(_env: Environment, instruction: Instruction): boolean {
function mayAllocate(env: Environment, instruction: Instruction): boolean {
const {value} = instruction;
switch (value.kind) {
case 'Destructure': {

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError} from '..';
import {
DeclarationId,
InstructionKind,
@@ -28,17 +27,7 @@ export function pruneHoistedContexts(fn: ReactiveFunction): void {
visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
}
const REWRITTEN_HOISTED_CONST: unique symbol = Symbol(
'REWRITTEN_HOISTED_CONST',
);
const REWRITTEN_HOISTED_LET: unique symbol = Symbol('REWRITTEN_HOISTED_LET');
type HoistedIdentifiers = Map<
DeclarationId,
| InstructionKind
| typeof REWRITTEN_HOISTED_CONST
| typeof REWRITTEN_HOISTED_LET
>;
type HoistedIdentifiers = Map<DeclarationId, InstructionKind>;
class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
override transformInstruction(
@@ -46,10 +35,6 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
state: HoistedIdentifiers,
): Transformed<ReactiveStatement> {
this.visitInstruction(instruction, state);
/**
* Remove hoisted declarations to preserve TDZ
*/
if (
instruction.value.kind === 'DeclareContext' &&
instruction.value.lvalue.kind === 'HoistedConst'
@@ -83,75 +68,31 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
return {kind: 'remove'};
}
if (instruction.value.kind === 'StoreContext') {
if (
instruction.value.kind === 'StoreContext' &&
state.has(instruction.value.lvalue.place.identifier.declarationId)
) {
const kind = state.get(
instruction.value.lvalue.place.identifier.declarationId,
);
if (kind != null) {
CompilerError.invariant(kind !== REWRITTEN_HOISTED_CONST, {
reason: 'Expected exactly one store to a hoisted const variable',
loc: instruction.loc,
});
if (
kind === InstructionKind.Const ||
kind === InstructionKind.Function
) {
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_CONST,
);
return {
kind: 'replace',
)!;
return {
kind: 'replace',
value: {
kind: 'instruction',
instruction: {
...instruction,
value: {
kind: 'instruction',
instruction: {
...instruction,
value: {
...instruction.value,
lvalue: {
...instruction.value.lvalue,
kind,
},
type: null,
kind: 'StoreLocal',
},
...instruction.value,
lvalue: {
...instruction.value.lvalue,
kind,
},
type: null,
kind: 'StoreLocal',
},
};
} else if (kind !== REWRITTEN_HOISTED_LET) {
/**
* Context variables declared with let may have reassignments. Only
* insert a `DeclareContext` for the first encountered `StoreContext`
* instruction.
*/
state.set(
instruction.value.lvalue.place.identifier.declarationId,
REWRITTEN_HOISTED_LET,
);
return {
kind: 'replace-many',
value: [
{
kind: 'instruction',
instruction: {
id: instruction.id,
lvalue: null,
value: {
kind: 'DeclareContext',
lvalue: {
kind: InstructionKind.Let,
place: {...instruction.value.lvalue.place},
},
loc: instruction.value.loc,
},
loc: instruction.loc,
},
},
{kind: 'instruction', instruction},
],
};
}
}
},
},
};
}
return {kind: 'keep'};

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {ProgramContext} from '..';
import {CompilerError} from '../CompilerError';
import {
DeclarationId,
@@ -48,7 +47,7 @@ import {ReactiveFunctionVisitor, visitReactiveFunction} from './visitors';
*/
export function renameVariables(fn: ReactiveFunction): Set<string> {
const globals = collectReferencedGlobals(fn);
const scopes = new Scopes(globals, fn.env.programContext);
const scopes = new Scopes(globals);
renameVariablesImpl(fn, new Visitor(), scopes);
return new Set([...scopes.names, ...globals]);
}
@@ -125,12 +124,10 @@ class Scopes {
#seen: Map<DeclarationId, IdentifierName> = new Map();
#stack: Array<Map<string, DeclarationId>> = [new Map()];
#globals: Set<string>;
#programContext: ProgramContext;
names: Set<ValidIdentifierName> = new Set();
constructor(globals: Set<string>, programContext: ProgramContext) {
constructor(globals: Set<string>) {
this.#globals = globals;
this.#programContext = programContext;
}
visit(identifier: Identifier): void {
@@ -159,7 +156,6 @@ class Scopes {
name = `${originalName.value}$${id++}`;
}
}
this.#programContext.addNewReference(name);
const identifierName = makeIdentifierName(name);
identifier.name = identifierName;
this.#seen.set(identifier.declarationId, identifierName);

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

@@ -28,7 +28,6 @@ import {
isUseEffectHookType,
LoadLocal,
makeInstructionId,
NonLocalImportSpecifier,
Place,
promoteTemporary,
} from '../HIR';
@@ -37,7 +36,6 @@ import {getOrInsertWith} from '../Utils/utils';
import {BuiltInFireId, DefaultNonmutatingHook} from '../HIR/ObjectShape';
import {eachInstructionOperand} from '../HIR/visitors';
import {printSourceLocationLine} from '../HIR/PrintHIR';
import {USE_FIRE_FUNCTION_NAME} from '../HIR/Environment';
/*
* TODO(jmbrown):
@@ -58,7 +56,6 @@ export function transformFire(fn: HIRFunction): void {
}
function replaceFireFunctions(fn: HIRFunction, context: Context): void {
let importedUseFire: NonLocalImportSpecifier | null = null;
let hasRewrite = false;
for (const [, block] of fn.body.blocks) {
const rewriteInstrs = new Map<InstructionId, Array<Instruction>>();
@@ -90,15 +87,7 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
] of capturedCallees.entries()) {
if (!context.hasCalleeWithInsertedFire(fireCalleePlace)) {
context.addCalleeWithInsertedFire(fireCalleePlace);
importedUseFire ??= fn.env.programContext.addImportSpecifier({
source: fn.env.programContext.reactRuntimeModule,
importSpecifierName: USE_FIRE_FUNCTION_NAME,
});
const loadUseFireInstr = makeLoadUseFireInstruction(
fn.env,
importedUseFire,
);
const loadUseFireInstr = makeLoadUseFireInstruction(fn.env);
const loadFireCalleeInstr = makeLoadFireCalleeInstruction(
fn.env,
fireCalleeInfo.capturedCalleeIdentifier,
@@ -415,16 +404,18 @@ function ensureNoMoreFireUses(fn: HIRFunction, context: Context): void {
}
}
function makeLoadUseFireInstruction(
env: Environment,
importedLoadUseFire: NonLocalImportSpecifier,
): Instruction {
function makeLoadUseFireInstruction(env: Environment): Instruction {
const useFirePlace = createTemporaryPlace(env, GeneratedSource);
useFirePlace.effect = Effect.Read;
useFirePlace.identifier.type = DefaultNonmutatingHook;
const instrValue: InstructionValue = {
kind: 'LoadGlobal',
binding: {...importedLoadUseFire},
binding: {
kind: 'ImportSpecifier',
name: 'useFire',
module: 'react',
imported: 'useFire',
},
loc: GeneratedSource,
};
return {

View File

@@ -261,7 +261,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@@ -278,7 +277,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: null,
return: returnType,
isConstructor: false,
});
yield equation(left, returnType);
break;
@@ -309,33 +307,17 @@ function* generateInstructionTypes(
kind: 'Property',
objectType: value.object.identifier.type,
objectName: getName(names, value.object.identifier.id),
propertyName: {
kind: 'literal',
value: value.property,
},
propertyName: value.property,
});
break;
}
case 'ComputedLoad': {
yield equation(left, {
kind: 'Property',
objectType: value.object.identifier.type,
objectName: getName(names, value.object.identifier.id),
propertyName: {
kind: 'computed',
value: value.property.identifier.type,
},
});
break;
}
case 'MethodCall': {
const returnType = makeType();
yield equation(value.property.identifier.type, {
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: false,
});
yield equation(left, returnType);
@@ -354,10 +336,7 @@ function* generateInstructionTypes(
kind: 'Property',
objectType: value.value.identifier.type,
objectName: getName(names, value.value.identifier.id),
propertyName: {
kind: 'literal',
value: makePropertyLiteral(propertyName),
},
propertyName: makePropertyLiteral(propertyName),
});
} else {
break;
@@ -374,10 +353,7 @@ function* generateInstructionTypes(
kind: 'Property',
objectType: value.value.identifier.type,
objectName: getName(names, value.value.identifier.id),
propertyName: {
kind: 'literal',
value: makePropertyLiteral(property.key.name),
},
propertyName: makePropertyLiteral(property.key.name),
});
}
}
@@ -408,7 +384,6 @@ function* generateInstructionTypes(
kind: 'Function',
shapeId: BuiltInFunctionId,
return: value.loweredFunc.func.returnType,
isConstructor: false,
});
break;
}
@@ -429,23 +404,13 @@ function* generateInstructionTypes(
yield equation(left, {kind: 'Object', shapeId: BuiltInJsxId});
break;
}
case 'NewExpression': {
const returnType = makeType();
yield equation(value.callee.identifier.type, {
kind: 'Function',
return: returnType,
shapeId: null,
isConstructor: true,
});
yield equation(left, returnType);
break;
}
case 'PropertyStore':
case 'DeclareLocal':
case 'NewExpression':
case 'RegExpLiteral':
case 'MetaProperty':
case 'ComputedStore':
case 'ComputedLoad':
case 'Await':
case 'GetIterator':
case 'IteratorNext':
@@ -489,13 +454,12 @@ class Unifier {
return;
}
const objectType = this.get(tB.objectType);
const propertyType =
tB.propertyName.kind === 'literal'
? this.env.getPropertyType(objectType, tB.propertyName.value)
: this.env.getFallthroughPropertyType(
objectType,
tB.propertyName.value,
);
let propertyType;
if (typeof tB.propertyName === 'number') {
propertyType = null;
} else {
propertyType = this.env.getPropertyType(objectType, tB.propertyName);
}
if (propertyType !== null) {
this.unify(tA, propertyType);
}
@@ -520,11 +484,7 @@ class Unifier {
return;
}
if (
tB.kind === 'Function' &&
tA.kind === 'Function' &&
tA.isConstructor === tB.isConstructor
) {
if (tB.kind === 'Function' && tA.kind === 'Function') {
this.unify(tA.return, tB.return);
return;
}
@@ -667,7 +627,6 @@ class Unifier {
kind: 'Function',
return: returnType,
shapeId: type.shapeId,
isConstructor: type.isConstructor,
};
}
case 'ObjectMethod':
@@ -718,11 +677,7 @@ class Unifier {
const RefLikeNameRE = /^(?:[a-zA-Z$_][a-zA-Z$_0-9]*)Ref$|^ref$/;
function isRefLikeName(t: PropType): boolean {
return (
t.propertyName.kind === 'literal' &&
RefLikeNameRE.test(t.objectName) &&
t.propertyName.value === 'current'
);
return RefLikeNameRE.test(t.objectName) && t.propertyName === 'current';
}
function tryUnionTypes(ty1: Type, ty2: Type): Type | null {

View File

@@ -8,7 +8,6 @@
import {CompilerError, ErrorSeverity} from '..';
import {HIRFunction} from '../HIR';
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
import {Result} from '../Utils/Result';
/**
* Checks that known-impure functions are not called during render. Examples of invalid functions to
@@ -19,9 +18,7 @@ import {Result} from '../Utils/Result';
* this in several of our validation passes and should unify those analyses into a reusable helper
* and use it here.
*/
export function validateNoImpureFunctionsInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
@@ -49,5 +46,7 @@ export function validateNoImpureFunctionsInRender(
}
}
}
return errors.asResult();
if (errors.hasErrors()) {
throw errors;
}
}

View File

@@ -26,7 +26,6 @@ import {
eachTerminalOperand,
} from '../HIR/visitors';
import {assertExhaustive} from '../Utils/utils';
import {Result} from '../Utils/Result';
/**
* Represents the possible kinds of value which may be stored at a given Place during
@@ -88,9 +87,7 @@ function joinKinds(a: Kind, b: Kind): Kind {
* may not appear as the callee of a conditional call.
* See the note for Kind.PotentialHook for sources of potential hooks
*/
export function validateHooksUsage(
fn: HIRFunction,
): Result<void, CompilerError> {
export function validateHooksUsage(fn: HIRFunction): void {
const unconditionalBlocks = computeUnconditionalBlocks(fn);
const errors = new CompilerError();
@@ -426,7 +423,9 @@ export function validateHooksUsage(
for (const [, error] of errorsByPlace) {
errors.push(error);
}
return errors.asResult();
if (errors.hasErrors()) {
throw errors;
}
}
function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {

View File

@@ -22,7 +22,6 @@ import {
ReactiveFunctionVisitor,
visitReactiveFunction,
} from '../ReactiveScopes/visitors';
import {Result} from '../Utils/Result';
/**
* Validates that all known effect dependencies are memoized. The algorithm checks two things:
@@ -48,12 +47,12 @@ import {Result} from '../Utils/Result';
* mutate(object); // ... mutable range ends here after this mutation
* ```
*/
export function validateMemoizedEffectDependencies(
fn: ReactiveFunction,
): Result<void, CompilerError> {
export function validateMemoizedEffectDependencies(fn: ReactiveFunction): void {
const errors = new CompilerError();
visitReactiveFunction(fn, new Visitor(), errors);
return errors.asResult();
if (errors.hasErrors()) {
throw errors;
}
}
class Visitor extends ReactiveFunctionVisitor<CompilerError> {

View File

@@ -4,14 +4,11 @@
* 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 {CompilerError, EnvironmentConfig} from '..';
import {HIRFunction, IdentifierId} from '../HIR';
import {DEFAULT_GLOBALS} from '../HIR/Globals';
import {Result} from '../Utils/Result';
export function validateNoCapitalizedCalls(
fn: HIRFunction,
): Result<void, CompilerError> {
export function validateNoCapitalizedCalls(fn: HIRFunction): void {
const envConfig: EnvironmentConfig = fn.env.config;
const ALLOW_LIST = new Set([
...DEFAULT_GLOBALS.keys(),
@@ -29,7 +26,6 @@ export function validateNoCapitalizedCalls(
);
};
const errors = new CompilerError();
const capitalLoadGlobals = new Map<IdentifierId, string>();
const capitalizedProperties = new Map<IdentifierId, string>();
const reason =
@@ -77,8 +73,7 @@ export function validateNoCapitalizedCalls(
const propertyIdentifier = value.property.identifier.id;
const propertyName = capitalizedProperties.get(propertyIdentifier);
if (propertyName != null) {
errors.push({
severity: ErrorSeverity.InvalidReact,
CompilerError.throwInvalidReact({
reason,
description: `${propertyName} may be a component.`,
loc: value.loc,
@@ -90,5 +85,4 @@ export function validateNoCapitalizedCalls(
}
}
}
return errors.asResult();
}

View File

@@ -7,7 +7,6 @@
import {CompilerError, ErrorSeverity} from '..';
import {BlockId, HIRFunction} from '../HIR';
import {Result} from '../Utils/Result';
import {retainWhere} from '../Utils/utils';
/**
@@ -20,9 +19,7 @@ import {retainWhere} from '../Utils/utils';
* created within a try block. JSX is allowed within a catch statement, unless that catch
* is itself nested inside an outer try.
*/
export function validateNoJSXInTryStatement(
fn: HIRFunction,
): Result<void, CompilerError> {
export function validateNoJSXInTryStatement(fn: HIRFunction): void {
const activeTryBlocks: Array<BlockId> = [];
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
@@ -49,5 +46,7 @@ export function validateNoJSXInTryStatement(
activeTryBlocks.push(block.terminal.handler);
}
}
return errors.asResult();
if (errors.hasErrors()) {
throw errors;
}
}

View File

@@ -99,11 +99,9 @@ class Env extends Map<IdentifierId, RefAccessType> {
}
}
export function validateNoRefAccessInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
export function validateNoRefAccessInRender(fn: HIRFunction): void {
const env = new Env();
return validateNoRefAccessInRenderImpl(fn, env).map(_ => undefined);
validateNoRefAccessInRenderImpl(fn, env).unwrap();
}
function refTypeOfType(place: Place): RefAccessType {

View File

@@ -14,7 +14,6 @@ import {
Place,
} from '../HIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {Result} from '../Utils/Result';
/**
* Validates against calling setState in the body of a *passive* effect (useEffect),
@@ -24,9 +23,7 @@ import {Result} from '../Utils/Result';
* often bad for performance and frequently has more efficient and straightforward
* alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
*/
export function validateNoSetStateInPassiveEffects(
fn: HIRFunction,
): Result<void, CompilerError> {
export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void {
const setStateFunctions: Map<IdentifierId, Place> = new Map();
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) {
@@ -101,7 +98,9 @@ export function validateNoSetStateInPassiveEffects(
}
}
return errors.asResult();
if (errors.hasErrors()) {
throw errors;
}
}
function getSetStateCall(

View File

@@ -9,7 +9,7 @@ import {CompilerError, ErrorSeverity} from '../CompilerError';
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {Result} from '../Utils/Result';
import {Err, Ok, Result} from '../Utils/Result';
/**
* Validates that the given function does not have an infinite update loop
@@ -39,11 +39,9 @@ import {Result} from '../Utils/Result';
* y();
* ```
*/
export function validateNoSetStateInRender(
fn: HIRFunction,
): Result<void, CompilerError> {
export function validateNoSetStateInRender(fn: HIRFunction): void {
const unconditionalSetStateFunctions: Set<IdentifierId> = new Set();
return validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions);
validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions).unwrap();
}
function validateNoSetStateInRenderImpl(
@@ -147,5 +145,9 @@ function validateNoSetStateInRenderImpl(
}
}
return errors.asResult();
if (errors.hasErrors()) {
return Err(errors);
} else {
return Ok(undefined);
}
}

View File

@@ -5,10 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {CompilerError, Effect, ErrorSeverity} from '..';
import {
DeclarationId,
Effect,
GeneratedSource,
Identifier,
IdentifierId,
@@ -31,7 +30,6 @@ import {
ReactiveFunctionVisitor,
visitReactiveFunction,
} from '../ReactiveScopes/visitors';
import {Result} from '../Utils/Result';
import {getOrInsertDefault} from '../Utils/utils';
/**
@@ -41,15 +39,15 @@ import {getOrInsertDefault} from '../Utils/utils';
* This can occur if a value's mutable range somehow extended to include a hook and
* was pruned.
*/
export function validatePreservedManualMemoization(
fn: ReactiveFunction,
): Result<void, CompilerError> {
export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
const state = {
errors: new CompilerError(),
manualMemoState: null,
};
visitReactiveFunction(fn, new Visitor(), state);
return state.errors.asResult();
if (state.errors.hasErrors()) {
throw state.errors;
}
}
const DEBUG = false;

View File

@@ -1,83 +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 {CompilerError, ErrorSeverity} from '../CompilerError';
import {HIRFunction, IdentifierId, SourceLocation} from '../HIR';
import {Result} from '../Utils/Result';
/**
* Validates against components that are created dynamically and whose identity is not guaranteed
* to be stable (which would cause the component to reset on each re-render).
*/
export function validateStaticComponents(
fn: HIRFunction,
): Result<void, CompilerError> {
const error = new CompilerError();
const knownDynamicComponents = new Map<IdentifierId, SourceLocation>();
for (const block of fn.body.blocks.values()) {
phis: for (const phi of block.phis) {
for (const operand of phi.operands.values()) {
const loc = knownDynamicComponents.get(operand.identifier.id);
if (loc != null) {
knownDynamicComponents.set(phi.place.identifier.id, loc);
continue phis;
}
}
}
for (const instr of block.instructions) {
const {lvalue, value} = instr;
switch (value.kind) {
case 'FunctionExpression':
case 'NewExpression':
case 'MethodCall':
case 'CallExpression': {
knownDynamicComponents.set(lvalue.identifier.id, value.loc);
break;
}
case 'LoadLocal': {
const loc = knownDynamicComponents.get(value.place.identifier.id);
if (loc != null) {
knownDynamicComponents.set(lvalue.identifier.id, loc);
}
break;
}
case 'StoreLocal': {
const loc = knownDynamicComponents.get(value.value.identifier.id);
if (loc != null) {
knownDynamicComponents.set(lvalue.identifier.id, loc);
knownDynamicComponents.set(value.lvalue.place.identifier.id, loc);
}
break;
}
case 'JsxExpression': {
if (value.tag.kind === 'Identifier') {
const location = knownDynamicComponents.get(
value.tag.identifier.id,
);
if (location != null) {
error.push({
reason: `Components created during render will reset their state each time they are created. Declare components outside of render. `,
severity: ErrorSeverity.InvalidReact,
loc: value.tag.loc,
description: null,
suggestions: null,
});
error.push({
reason: `The component may be created during render`,
severity: ErrorSeverity.InvalidReact,
loc: location,
description: null,
suggestions: null,
});
}
}
}
}
}
}
return error.asResult();
}

View File

@@ -5,12 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {CompilerError} from '..';
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
import {Result} from '../Utils/Result';
export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
const errors = new CompilerError();
export function validateUseMemo(fn: HIRFunction): void {
const useMemos = new Set<IdentifierId>();
const react = new Set<IdentifierId>();
const functions = new Map<IdentifierId, FunctionExpression>();
@@ -63,8 +61,7 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
}
if (body.loweredFunc.func.params.length > 0) {
errors.push({
severity: ErrorSeverity.InvalidReact,
CompilerError.throwInvalidReact({
reason: 'useMemo callbacks may not accept any arguments',
description: null,
loc: body.loc,
@@ -73,8 +70,7 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
}
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
errors.push({
severity: ErrorSeverity.InvalidReact,
CompilerError.throwInvalidReact({
reason:
'useMemo callbacks may not be async or generator functions',
description: null,
@@ -88,5 +84,4 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
}
}
}
return errors.asResult();
}

View File

@@ -12,7 +12,6 @@ globalThis.constantValue = 'global test value';
test('literal-constant-propagation', () => {
function Component() {
'use memo';
const x = 'test value 1';
return <div>{x}</div>;
}
@@ -39,7 +38,6 @@ test('literal-constant-propagation', () => {
test('global-constant-propagation', () => {
function Component() {
'use memo';
const x = constantValue;
return <div>{x}</div>;
@@ -67,7 +65,6 @@ test('global-constant-propagation', () => {
test('lambda-constant-propagation', () => {
function Component() {
'use memo';
const x = 'test value 1';
const getDiv = () => <div>{x}</div>;
return getDiv();
@@ -95,7 +92,6 @@ test('lambda-constant-propagation', () => {
test('lambda-constant-propagation-of-phi-node', () => {
function Component({noopCallback}) {
'use memo';
const x = 'test value 1';
if (constantValue) {
noopCallback();

View File

@@ -16,7 +16,6 @@ function Button({label}) {
let currentTheme = 'light';
function useTheme() {
'use memo';
return currentTheme;
}

View File

@@ -10,7 +10,6 @@ import * as React from 'react';
import {expectLogsAndClear, log} from './expectLogs';
function Counter(props) {
'use memo';
let value = props.value;
let a = value++;
expect(a).toBe(props.value); // postfix

View File

@@ -1,118 +0,0 @@
## Input
```javascript
import {useIdentity, Stringify} from 'shared-runtime';
/**
* TODO: Note that this `Array.from` is inferred to be mutating its first
* argument. This is because React Compiler's typing system does not yet support
* annotating a function with a set of argument match cases + distinct
* definitions (polymorphism).
*
* In this case, we should be able to infer that the `Array.from` call is
* not mutating its 0th argument.
* The 0th argument should be typed as having `effect:Mutate` only when
* (1) it might be a mutable iterable or
* (2) the 1st argument might mutate its callee
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr, (x, idx) => ({...x, id: idx}));
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useIdentity, Stringify } from "shared-runtime";
/**
* TODO: Note that this `Array.from` is inferred to be mutating its first
* argument. This is because React Compiler's typing system does not yet support
* annotating a function with a set of argument match cases + distinct
* definitions (polymorphism).
*
* In this case, we should be able to infer that the `Array.from` call is
* not mutating its 0th argument.
* The 0th argument should be typed as having `effect:Mutate` only when
* (1) it might be a mutable iterable or
* (2) the 1st argument might mutate its callee
*/
function Component(t0) {
const $ = _c(10);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
useIdentity();
let t4;
if ($[4] !== arr) {
t4 = Array.from(arr, _temp);
$[4] = arr;
$[5] = t4;
} else {
t4 = $[5];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(-1);
$[6] = derived;
$[7] = t5;
} else {
t5 = $[7];
}
let t6;
if ($[8] !== t5) {
t6 = <Stringify>{t5}</Stringify>;
$[8] = t5;
$[9] = t6;
} else {
t6 = $[9];
}
return t6;
}
function _temp(x, idx) {
return { ...x, id: idx };
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":{"value":5,"id":2}}</div>
<div>{"children":{"value":6,"id":2}}</div>
<div>{"children":{"value":6,"id":2}}</div>

View File

@@ -1,26 +0,0 @@
import {useIdentity, Stringify} from 'shared-runtime';
/**
* TODO: Note that this `Array.from` is inferred to be mutating its first
* argument. This is because React Compiler's typing system does not yet support
* annotating a function with a set of argument match cases + distinct
* definitions (polymorphism).
*
* In this case, we should be able to infer that the `Array.from` call is
* not mutating its 0th argument.
* The 0th argument should be typed as having `effect:Mutate` only when
* (1) it might be a mutable iterable or
* (2) the 1st argument might mutate its callee
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr, (x, idx) => ({...x, id: idx}));
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,115 +0,0 @@
## Input
```javascript
import {useIdentity, Stringify} from 'shared-runtime';
/**
* TODO: Note that this `Array.from` is inferred to be mutating its first
* argument. This is because React Compiler's typing system does not yet support
* annotating a function with a set of argument match cases + distinct
* definitions (polymorphism)
*
* In this case, we should be able to infer that the `Array.from` call is
* not mutating its 0th argument.
* The 0th argument should be typed as having `effect:Mutate` only when
* (1) it might be a mutable iterable or
* (2) the 1st argument might mutate its callee
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr);
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useIdentity, Stringify } from "shared-runtime";
/**
* TODO: Note that this `Array.from` is inferred to be mutating its first
* argument. This is because React Compiler's typing system does not yet support
* annotating a function with a set of argument match cases + distinct
* definitions (polymorphism)
*
* In this case, we should be able to infer that the `Array.from` call is
* not mutating its 0th argument.
* The 0th argument should be typed as having `effect:Mutate` only when
* (1) it might be a mutable iterable or
* (2) the 1st argument might mutate its callee
*/
function Component(t0) {
const $ = _c(10);
const { value } = t0;
let t1;
let t2;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = { value: "foo" };
t2 = { value: "bar" };
$[0] = t1;
$[1] = t2;
} else {
t1 = $[0];
t2 = $[1];
}
let t3;
if ($[2] !== value) {
t3 = [t1, t2, { value }];
$[2] = value;
$[3] = t3;
} else {
t3 = $[3];
}
const arr = t3;
useIdentity();
let t4;
if ($[4] !== arr) {
t4 = Array.from(arr);
$[4] = arr;
$[5] = t4;
} else {
t4 = $[5];
}
const derived = t4;
let t5;
if ($[6] !== derived) {
t5 = derived.at(-1);
$[6] = derived;
$[7] = t5;
} else {
t5 = $[7];
}
let t6;
if ($[8] !== t5) {
t6 = <Stringify>{t5}</Stringify>;
$[8] = t5;
$[9] = t6;
} else {
t6 = $[9];
}
return t6;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
};
```
### Eval output
(kind: ok) <div>{"children":{"value":5}}</div>
<div>{"children":{"value":6}}</div>
<div>{"children":{"value":6}}</div>

View File

@@ -1,26 +0,0 @@
import {useIdentity, Stringify} from 'shared-runtime';
/**
* TODO: Note that this `Array.from` is inferred to be mutating its first
* argument. This is because React Compiler's typing system does not yet support
* annotating a function with a set of argument match cases + distinct
* definitions (polymorphism)
*
* In this case, we should be able to infer that the `Array.from` call is
* not mutating its 0th argument.
* The 0th argument should be typed as having `effect:Mutate` only when
* (1) it might be a mutable iterable or
* (2) the 1st argument might mutate its callee
*/
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr);
return <Stringify>{derived.at(-1)}</Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
};

View File

@@ -1,84 +0,0 @@
## Input
```javascript
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
function Component(t0) {
const $ = _c(7);
const { value } = t0;
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
let t1;
if ($[0] !== derived) {
t1 = derived.at(0);
$[0] = derived;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] !== derived) {
t2 = derived.at(-1);
$[2] = derived;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = (
<Stringify>
{t1}
{t2}
</Stringify>
);
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 5 }],
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
};
```
### Eval output
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":7,"wat0":"joe"}]}</div>

View File

@@ -1,19 +0,0 @@
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
function Component({value}) {
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
useIdentity();
const derived = Array.from(arr).map(mutateAndReturn);
return (
<Stringify>
{derived.at(0)}
{derived.at(-1)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 5}],
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
};

View File

@@ -1,62 +0,0 @@
## Input
```javascript
function useBar({arg}) {
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
const arr = [...mutableIterator];
obj.x = arg;
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useBar(t0) {
const $ = _c(2);
const { arg } = t0;
let arr;
if ($[0] !== arg) {
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
arr = [...mutableIterator];
obj.x = arg;
$[0] = arg;
$[1] = arr;
} else {
arr = $[1];
}
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{ arg: 3 }],
sequentialRenders: [{ arg: 3 }, { arg: 3 }, { arg: 4 }],
};
```
### Eval output
(kind: ok) [{"x":3},5,4]
[{"x":3},5,4]
[{"x":4},5,4]

View File

@@ -1,20 +0,0 @@
function useBar({arg}) {
/**
* Note that mutableIterator is mutated by the later object spread. Therefore,
* `s.values()` should be memoized within the same block as the object spread.
* In terms of compiler internals, they should have the same reactive scope.
*/
const obj = {};
const s = new Set([obj, 5, 4]);
const mutableIterator = s.values();
const arr = [...mutableIterator];
obj.x = arg;
return arr;
}
export const FIXTURE_ENTRYPOINT = {
fn: useBar,
params: [{arg: 3}],
sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
};

View File

@@ -0,0 +1,35 @@
## Input
```javascript
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;
```
## Code
```javascript
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
import { c as _c } from "react/compiler-runtime"; // @gating
const ErrorView = isForgetEnabled_Fixtures()
? (error, _retry) => {
const $ = _c(2);
let t0;
if ($[0] !== error) {
t0 = <MessageBox error={error} />;
$[0] = error;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
: (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;
```

View File

@@ -0,0 +1,4 @@
// @gating
const ErrorView = (error, _retry) => <MessageBox error={error}></MessageBox>;
export default ErrorView;

View File

@@ -1,39 +0,0 @@
## Input
```javascript
// @compilationMode(infer)
const Test = () => <div />;
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
const Test = () => {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
};
export const FIXTURE_ENTRYPOINT = {
fn: Test,
params: [{}],
};
```
### Eval output
(kind: ok) <div></div>

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