Compare commits
1 Commits
sebbie/rea
...
sync-nextj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6230622a1a |
699
.circleci/config.yml
Normal file
699
.circleci/config.yml
Normal file
@@ -0,0 +1,699 @@
|
||||
version: 2.1
|
||||
|
||||
aliases:
|
||||
- &docker
|
||||
- image: cimg/node:18.20.1-browsers
|
||||
|
||||
- &environment
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
- &restore_yarn_cache_fixtures_dom
|
||||
restore_cache:
|
||||
name: Restore yarn cache for fixtures/dom
|
||||
keys:
|
||||
- v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}-fixtures/dom
|
||||
|
||||
- &yarn_install_fixtures_dom
|
||||
run:
|
||||
name: Install dependencies in fixtures/dom
|
||||
working_directory: fixtures/dom
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
|
||||
- &yarn_install_fixtures_dom_retry
|
||||
run:
|
||||
name: Install dependencies in fixtures/dom (retry)
|
||||
when: on_fail
|
||||
working_directory: fixtures/dom
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
|
||||
- &save_yarn_cache_fixtures_dom
|
||||
save_cache:
|
||||
name: Save yarn cache for fixtures/dom
|
||||
key: v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}-fixtures/dom
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
- &TEST_PARALLELISM 20
|
||||
|
||||
- &attach_workspace
|
||||
at: build
|
||||
|
||||
commands:
|
||||
setup_node_modules:
|
||||
description: "Restore node_modules"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore yarn cache
|
||||
keys:
|
||||
- v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
if [ $? -ne 0 ]; then
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
fi
|
||||
environment:
|
||||
# If we start needing the Electron binary, please ensure the binary is cached in CI following https://www.electronjs.org/docs/latest/tutorial/installation
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
- save_cache:
|
||||
name: Save yarn cache
|
||||
key: v2-yarn_cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
# The CircleCI API doesn't yet support triggering a specific workflow, but it
|
||||
# does support triggering a pipeline. So as a workaround you can triggger the
|
||||
# entire pipeline and use parameters to disable everything except the workflow
|
||||
# you want. CircleCI recommends this workaround here:
|
||||
# https://support.circleci.com/hc/en-us/articles/360050351292-How-to-trigger-a-workflow-via-CircleCI-API-v2-
|
||||
parameters:
|
||||
# This is only set when triggering the CI pipeline via an API request.
|
||||
prerelease_commit_sha:
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
yarn_lint:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: node ./scripts/prettier/index
|
||||
- run: node ./scripts/tasks/eslint
|
||||
- run: ./scripts/circleci/check_license.sh
|
||||
- run: ./scripts/circleci/test_print_warnings.sh
|
||||
|
||||
yarn_flow:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 5
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: node ./scripts/tasks/flow-ci
|
||||
|
||||
|
||||
yarn_flags:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: yarn flags
|
||||
|
||||
scrape_warning_messages:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
command: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- build
|
||||
|
||||
yarn_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: 40
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: yarn build
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- build
|
||||
|
||||
download_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parameters:
|
||||
revision:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Download artifacts for revision
|
||||
command: |
|
||||
git fetch origin main
|
||||
cd ./scripts/release && yarn && cd ../../
|
||||
scripts/release/download-experimental-build.js --commit=<< parameters.revision >> --allowBrokenCI
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- build
|
||||
|
||||
download_base_build_for_sizebot:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Download artifacts for base revision
|
||||
command: |
|
||||
git fetch origin main
|
||||
cd ./scripts/release && yarn && cd ../../
|
||||
scripts/release/download-experimental-build.js --commit=$(git merge-base HEAD origin/main) --allowBrokenCI
|
||||
mv ./build ./base-build
|
||||
|
||||
- run:
|
||||
# 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
|
||||
command: rm -rf ./base-build/node_modules
|
||||
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- base-build
|
||||
|
||||
process_artifacts_combined:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: echo "<< pipeline.git.revision >>" >> build/COMMIT_SHA
|
||||
# Compress build directory into a single tarball for easy download
|
||||
- run: tar -zcvf ./build.tgz ./build
|
||||
# TODO: Migrate scripts to use `build` directory instead of `build2`
|
||||
- run: cp ./build.tgz ./build2.tgz
|
||||
- store_artifacts:
|
||||
path: ./build2.tgz
|
||||
- store_artifacts:
|
||||
path: ./build.tgz
|
||||
|
||||
sizebot:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: echo "<< pipeline.git.revision >>" >> build/COMMIT_SHA
|
||||
- setup_node_modules
|
||||
- run:
|
||||
command: node ./scripts/tasks/danger
|
||||
- store_artifacts:
|
||||
path: sizebot-message.md
|
||||
|
||||
build_devtools_and_process_artifacts:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
|
||||
- store_artifacts:
|
||||
path: ./build/devtools.tgz
|
||||
# Simplifies getting the extension for local testing
|
||||
- store_artifacts:
|
||||
path: ./build/devtools/chrome-extension.zip
|
||||
destination: react-devtools-chrome-extension.zip
|
||||
- store_artifacts:
|
||||
path: ./build/devtools/firefox-extension.zip
|
||||
destination: react-devtools-firefox-extension.zip
|
||||
|
||||
run_devtools_e2e_tests:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Playwright install deps
|
||||
command: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: ./scripts/circleci/run_devtools_e2e_tests.js
|
||||
|
||||
run_fixtures_flight_tests:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
# Fixture copies some built packages from the workroot after install.
|
||||
# That means dependencies of the built packages are not installed.
|
||||
# We need to install dependencies of the workroot to fulfill all dependency constraints
|
||||
- setup_node_modules
|
||||
- restore_cache:
|
||||
name: Restore yarn cache of fixture
|
||||
keys:
|
||||
- v2-yarn_cache_fixtures_flight-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install fixture dependencies
|
||||
working_directory: fixtures/flight
|
||||
command: |
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
if [ $? -ne 0 ]; then
|
||||
yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
fi
|
||||
- save_cache:
|
||||
name: Save yarn cache of fixture
|
||||
key: v2-yarn_cache_fixtures_flight-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
- run:
|
||||
working_directory: fixtures/flight
|
||||
name: Playwright install deps
|
||||
command: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run:
|
||||
name: Run tests
|
||||
working_directory: fixtures/flight
|
||||
command: yarn test
|
||||
environment:
|
||||
# Otherwise the webserver is a blackbox
|
||||
DEBUG: pw:webserver
|
||||
- store_artifacts:
|
||||
path: fixtures/flight/playwright-report
|
||||
- store_artifacts:
|
||||
path: fixtures/flight/test-results
|
||||
|
||||
run_devtools_tests_for_versions:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
version:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: ./scripts/circleci/download_devtools_regression_build.js << parameters.version >> --replaceBuild
|
||||
- run: node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion << parameters.version >> --ci
|
||||
|
||||
run_devtools_e2e_tests_for_versions:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
version:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Playwright install deps
|
||||
command: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run: ./scripts/circleci/download_devtools_regression_build.js << parameters.version >>
|
||||
- run:
|
||||
environment:
|
||||
RELEASE_CHANNEL: experimental
|
||||
command: ./scripts/circleci/run_devtools_e2e_tests.js << parameters.version >>
|
||||
- run:
|
||||
name: Cleanup build regression folder
|
||||
command: rm -r ./build-regression
|
||||
- store_artifacts:
|
||||
path: ./tmp/screenshots
|
||||
|
||||
yarn_lint_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: yarn lint-build
|
||||
|
||||
yarn_check_release_dependencies:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: yarn check-release-dependencies
|
||||
|
||||
|
||||
check_error_codes:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Search build artifacts for unminified errors
|
||||
command: |
|
||||
yarn extract-errors
|
||||
git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
|
||||
|
||||
check_generated_fizz_runtime:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace: *attach_workspace
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Confirm generated inline Fizz runtime is up to date
|
||||
command: |
|
||||
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)
|
||||
|
||||
yarn_test:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
args:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run: yarn test <<parameters.args>> --ci
|
||||
|
||||
yarn_test_build:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
parallelism: *TEST_PARALLELISM
|
||||
parameters:
|
||||
args:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- run: yarn test --build <<parameters.args>> --ci
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- setup_node_modules
|
||||
- *restore_yarn_cache_fixtures_dom
|
||||
- *yarn_install_fixtures_dom
|
||||
- *yarn_install_fixtures_dom_retry
|
||||
- *save_yarn_cache_fixtures_dom
|
||||
- run:
|
||||
name: Run DOM fixture tests
|
||||
environment:
|
||||
RELEASE_CHANNEL: stable
|
||||
working_directory: fixtures/dom
|
||||
command: |
|
||||
yarn predev
|
||||
yarn test --maxWorkers=2
|
||||
|
||||
publish_prerelease:
|
||||
parameters:
|
||||
commit_sha:
|
||||
type: string
|
||||
release_channel:
|
||||
type: string
|
||||
dist_tag:
|
||||
type: string
|
||||
docker: *docker
|
||||
environment: *environment
|
||||
steps:
|
||||
- checkout
|
||||
- setup_node_modules
|
||||
- run:
|
||||
name: Run publish script
|
||||
command: |
|
||||
git fetch origin main
|
||||
cd ./scripts/release && yarn && cd ../../
|
||||
scripts/release/prepare-release-from-ci.js --skipTests -r << parameters.release_channel >> --commit=<< parameters.commit_sha >>
|
||||
cp ./scripts/release/ci-npmrc ~/.npmrc
|
||||
scripts/release/publish.js --ci --tags << parameters.dist_tag >>
|
||||
|
||||
workflows:
|
||||
|
||||
build_and_test:
|
||||
unless: << pipeline.parameters.prerelease_commit_sha >>
|
||||
jobs:
|
||||
- yarn_flags:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- yarn_flow:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- check_generated_fizz_runtime:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- yarn_lint:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- yarn_test:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
matrix:
|
||||
parameters:
|
||||
args:
|
||||
# Intentionally passing these as strings instead of creating a
|
||||
# separate parameter per CLI argument, since it's easier to
|
||||
# control/see which combinations we want to run.
|
||||
- "-r=stable --env=development"
|
||||
- "-r=stable --env=production"
|
||||
- "-r=experimental --env=development"
|
||||
- "-r=experimental --env=production"
|
||||
- "-r=www-classic --env=development --variant=false"
|
||||
- "-r=www-classic --env=production --variant=false"
|
||||
- "-r=www-classic --env=development --variant=true"
|
||||
- "-r=www-classic --env=production --variant=true"
|
||||
- "-r=www-modern --env=development --variant=false"
|
||||
- "-r=www-modern --env=production --variant=false"
|
||||
- "-r=www-modern --env=development --variant=true"
|
||||
- "-r=www-modern --env=production --variant=true"
|
||||
- "-r=xplat --env=development --variant=false"
|
||||
- "-r=xplat --env=development --variant=true"
|
||||
- "-r=xplat --env=production --variant=false"
|
||||
- "-r=xplat --env=production --variant=true"
|
||||
|
||||
# TODO: Test more persistent configurations?
|
||||
- '-r=stable --env=development --persistent'
|
||||
- '-r=experimental --env=development --persistent'
|
||||
- yarn_build:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- scrape_warning_messages:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- builds/facebook-www
|
||||
- process_artifacts_combined:
|
||||
requires:
|
||||
- scrape_warning_messages
|
||||
- yarn_build
|
||||
- yarn_test_build:
|
||||
requires:
|
||||
- yarn_build
|
||||
matrix:
|
||||
parameters:
|
||||
args:
|
||||
# Intentionally passing these as strings instead of creating a
|
||||
# separate parameter per CLI argument, since it's easier to
|
||||
# control/see which combinations we want to run.
|
||||
- "-r=stable --env=development"
|
||||
- "-r=stable --env=production"
|
||||
- "-r=experimental --env=development"
|
||||
- "-r=experimental --env=production"
|
||||
|
||||
# Dev Tools
|
||||
- "--project=devtools -r=experimental"
|
||||
|
||||
# TODO: Update test config to support www build tests
|
||||
# - "-r=www-classic --env=development --variant=false"
|
||||
# - "-r=www-classic --env=production --variant=false"
|
||||
# - "-r=www-classic --env=development --variant=true"
|
||||
# - "-r=www-classic --env=production --variant=true"
|
||||
# - "-r=www-modern --env=development --variant=false"
|
||||
# - "-r=www-modern --env=production --variant=false"
|
||||
# - "-r=www-modern --env=development --variant=true"
|
||||
# - "-r=www-modern --env=production --variant=true"
|
||||
|
||||
# TODO: Update test config to support xplat build tests
|
||||
# - "-r=xplat --env=development --variant=false"
|
||||
# - "-r=xplat --env=development --variant=true"
|
||||
# - "-r=xplat --env=production --variant=false"
|
||||
# - "-r=xplat --env=production --variant=true"
|
||||
|
||||
# TODO: Test more persistent configurations?
|
||||
- download_base_build_for_sizebot:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- main
|
||||
- builds/facebook-www
|
||||
- sizebot:
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- main
|
||||
requires:
|
||||
- download_base_build_for_sizebot
|
||||
- yarn_build
|
||||
- yarn_lint_build:
|
||||
requires:
|
||||
- yarn_build
|
||||
- yarn_check_release_dependencies:
|
||||
requires:
|
||||
- yarn_build
|
||||
- check_error_codes:
|
||||
requires:
|
||||
- yarn_build
|
||||
- RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
requires:
|
||||
- yarn_build
|
||||
- build_devtools_and_process_artifacts:
|
||||
requires:
|
||||
- yarn_build
|
||||
- run_devtools_e2e_tests:
|
||||
requires:
|
||||
- build_devtools_and_process_artifacts
|
||||
- run_fixtures_flight_tests:
|
||||
requires:
|
||||
- yarn_build
|
||||
|
||||
devtools_regression_tests:
|
||||
unless: << pipeline.parameters.prerelease_commit_sha >>
|
||||
triggers:
|
||||
- schedule:
|
||||
# DevTools regression tests run once a day
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- download_build:
|
||||
revision: << pipeline.git.revision >>
|
||||
- build_devtools_and_process_artifacts:
|
||||
requires:
|
||||
- download_build
|
||||
- run_devtools_tests_for_versions:
|
||||
requires:
|
||||
- build_devtools_and_process_artifacts
|
||||
matrix:
|
||||
parameters:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
- run_devtools_e2e_tests_for_versions:
|
||||
requires:
|
||||
- build_devtools_and_process_artifacts
|
||||
matrix:
|
||||
parameters:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
|
||||
# Used to publish a prerelease manually via the command line
|
||||
publish_preleases:
|
||||
when: << pipeline.parameters.prerelease_commit_sha >>
|
||||
jobs:
|
||||
- publish_prerelease:
|
||||
name: Publish to Canary channel
|
||||
commit_sha: << pipeline.parameters.prerelease_commit_sha >>
|
||||
release_channel: stable
|
||||
# The tags to use when publishing canaries. The main one we should
|
||||
# always include is "canary" but we can use multiple (e.g. alpha,
|
||||
# beta, rc). To declare multiple, use a comma-separated string, like
|
||||
# this:
|
||||
# dist_tag: "canary,alpha,beta,rc"
|
||||
#
|
||||
# TODO: We currently tag canaries with "next" in addition to "canary"
|
||||
# because this used to be called the "next" channel and some
|
||||
# downstream consumers might still expect that tag. We can remove this
|
||||
# after some time has elapsed and the change has been communicated.
|
||||
dist_tag: "canary,next,rc"
|
||||
- publish_prerelease:
|
||||
name: Publish to Experimental channel
|
||||
requires:
|
||||
# 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
|
||||
# dist tags.
|
||||
- Publish to Canary channel
|
||||
commit_sha: << pipeline.parameters.prerelease_commit_sha >>
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
|
||||
# Publishes on a cron schedule
|
||||
publish_preleases_nightly:
|
||||
unless: << pipeline.parameters.prerelease_commit_sha >>
|
||||
triggers:
|
||||
- schedule:
|
||||
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
|
||||
cron: "10 16 * * 1,2,3,4,5"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- publish_prerelease:
|
||||
name: Publish to Canary channel
|
||||
commit_sha: << pipeline.git.revision >>
|
||||
release_channel: stable
|
||||
dist_tag: "canary,next,rc"
|
||||
- publish_prerelease:
|
||||
name: Publish to Experimental channel
|
||||
requires:
|
||||
# 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
|
||||
# dist tags.
|
||||
- Publish to Canary channel
|
||||
commit_sha: << pipeline.git.revision >>
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
57
.eslintrc.js
57
.eslintrc.js
@@ -12,13 +12,11 @@ const WARNING = 1;
|
||||
const ERROR = 2;
|
||||
|
||||
module.exports = {
|
||||
extends: ['prettier', 'plugin:jest/recommended'],
|
||||
extends: ['prettier'],
|
||||
|
||||
// Stop ESLint from looking for a configuration file in parent folders
|
||||
root: true,
|
||||
|
||||
reportUnusedDisableDirectives: true,
|
||||
|
||||
plugins: [
|
||||
'babel',
|
||||
'ft-flow',
|
||||
@@ -247,7 +245,7 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'no-shadow': ERROR,
|
||||
'no-unused-vars': [ERROR, {args: 'none', ignoreRestSiblings: true}],
|
||||
'no-unused-vars': [ERROR, {args: 'none'}],
|
||||
'no-use-before-define': OFF,
|
||||
'no-useless-concat': OFF,
|
||||
quotes: [ERROR, 'single', {avoidEscape: true, allowTemplateLiterals: true}],
|
||||
@@ -378,49 +376,16 @@ module.exports = {
|
||||
files: ['**/__tests__/*.js'],
|
||||
rules: {
|
||||
// https://github.com/jest-community/eslint-plugin-jest
|
||||
// Meh, who cares.
|
||||
'jest/consistent-test-it': OFF,
|
||||
// Meh, we have a lot of these, who cares.
|
||||
'jest/no-alias-methods': OFF,
|
||||
// We do conditions based on feature flags.
|
||||
'jest/no-conditional-expect': OFF,
|
||||
// We have our own assertion helpers.
|
||||
'jest/expect-expect': OFF,
|
||||
// Lame rule that fires in itRender helpers or in render methods.
|
||||
'jest/no-standalone-expect': OFF,
|
||||
'jest/no-focused-tests': ERROR,
|
||||
'jest/valid-expect': ERROR,
|
||||
'jest/valid-expect-in-promise': ERROR,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Rules specific to test setup helper files.
|
||||
files: [
|
||||
'**/setupTests.js',
|
||||
'**/setupEnv.js',
|
||||
'**/jest/TestFlags.js',
|
||||
'**/dom-event-testing-library/testHelpers.js',
|
||||
'**/utils/ReactDOMServerIntegrationTestUtils.js',
|
||||
'**/babel/transform-react-version-pragma.js',
|
||||
'**/babel/transform-test-gate-pragma.js',
|
||||
],
|
||||
// disable no focused tests for test setup helper files even if they are inside __tests__ directory
|
||||
files: ['**/setupTests.js'],
|
||||
rules: {
|
||||
// Some helpers intentionally focus tests.
|
||||
'jest/no-focused-tests': OFF,
|
||||
// Test fn helpers don't use static text names.
|
||||
'jest/valid-title': OFF,
|
||||
// We have our own assertion helpers.
|
||||
'jest/expect-expect': OFF,
|
||||
// Some helpers intentionally disable tests.
|
||||
'jest/no-disabled-tests': OFF,
|
||||
// Helpers export text function helpers.
|
||||
'jest/no-export': OFF,
|
||||
// The examples in comments trigger false errors.
|
||||
'jest/no-commented-out-tests': OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/jest/TestFlags.js'],
|
||||
rules: {
|
||||
// The examples in comments trigger false errors.
|
||||
'jest/no-commented-out-tests': OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -486,12 +451,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'packages/react-devtools-extensions/**/*.js',
|
||||
'packages/react-devtools-shared/src/hook.js',
|
||||
'packages/react-devtools-shared/src/backend/console.js',
|
||||
'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
|
||||
],
|
||||
files: ['packages/react-devtools-extensions/**/*.js'],
|
||||
globals: {
|
||||
__IS_CHROME__: 'readonly',
|
||||
__IS_FIREFOX__: 'readonly',
|
||||
@@ -578,7 +538,6 @@ module.exports = {
|
||||
TimeoutID: 'readonly',
|
||||
WheelEventHandler: 'readonly',
|
||||
FinalizationRegistry: 'readonly',
|
||||
Omit: 'readonly',
|
||||
|
||||
spyOnDev: 'readonly',
|
||||
spyOnDevAndProd: 'readonly',
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
c998bb1ed4b3285398c9c7797135d3f060243c6a
|
||||
fd2b3e13d330a4559f5aa21462e1cb2cbbcf144b
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
name: (Runtime) Commit Artifacts for Meta WWW and fbsource V2
|
||||
name: Commit Artifacts for Meta WWW and fbsource
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["(Runtime) Build and Test"]
|
||||
types: [completed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
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
|
||||
push:
|
||||
branches: [main, meta-www, meta-fbsource]
|
||||
|
||||
jobs:
|
||||
download_artifacts:
|
||||
@@ -59,27 +46,101 @@ 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"
|
||||
- uses: actions/setup-node@v4
|
||||
- name: Download and unzip artifacts
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
CIRCLECI_TOKEN: ${{secrets.CIRCLECI_TOKEN_DIFFTRAIN}}
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
|
||||
- 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 }}
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
script: |
|
||||
const cp = require('child_process');
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function execHelper(command, options, streamStdout = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = cp.exec(
|
||||
command,
|
||||
options,
|
||||
(error, stdout) => (error ? reject(error) : resolve(stdout.trim())),
|
||||
);
|
||||
if (streamStdout) {
|
||||
proc.stdout.pipe(process.stdout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let artifactsUrl = null;
|
||||
// This is a temporary, dirty hack to avoid needing a GitHub auth token in the circleci
|
||||
// workflow to notify this GitHub action. Sorry!
|
||||
let iter = 0;
|
||||
spinloop: while (iter < 15) {
|
||||
const res = await github.rest.repos.listCommitStatusesForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha
|
||||
});
|
||||
for (const status of res.data) {
|
||||
if (/process_artifacts_combined/.test(status.context)) {
|
||||
switch (status.state) {
|
||||
case 'pending': {
|
||||
console.log(`${status.context} is still pending`);
|
||||
break;
|
||||
}
|
||||
case 'failure':
|
||||
case 'error': {
|
||||
throw new Error(`${status.context} has failed or errored`);
|
||||
}
|
||||
case 'success': {
|
||||
// The status does not include a build ID, but we can extract it
|
||||
// from the URL. I couldn't find a better way to do this.
|
||||
const ciBuildId = /\/facebook\/react\/([0-9]+)/.exec(
|
||||
status.target_url,
|
||||
)[1];
|
||||
if (Number.parseInt(ciBuildId, 10) + '' === ciBuildId) {
|
||||
artifactsUrl =
|
||||
`https://circleci.com/api/v1.1/project/github/facebook/react/${ciBuildId}/artifacts`;
|
||||
console.log(`Found artifactsUrl: ${artifactsUrl}`);
|
||||
break spinloop;
|
||||
} else {
|
||||
throw new Error(`${ciBuildId} isn't a number`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unhandled status state: ${status.state}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
iter++;
|
||||
console.log("Sleeping for 60s...");
|
||||
await sleep(60_000);
|
||||
}
|
||||
if (artifactsUrl != null) {
|
||||
const {CIRCLECI_TOKEN} = process.env;
|
||||
const res = await fetch(artifactsUrl, {
|
||||
headers: {
|
||||
'Circle-Token': CIRCLECI_TOKEN
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!Array.isArray(data) && data.message != null) {
|
||||
throw `CircleCI returned: ${data.message}`;
|
||||
}
|
||||
for (const artifact of data) {
|
||||
if (artifact.path === 'build.tgz') {
|
||||
console.log(`Downloading and unzipping ${artifact.url}`);
|
||||
await execHelper(
|
||||
`curl -L ${artifact.url} -H "Circle-Token: ${CIRCLECI_TOKEN}" | tar -xvz`
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
- name: Strip @license from eslint plugin and react-refresh
|
||||
run: |
|
||||
sed -i -e 's/ @license React*//' \
|
||||
@@ -134,9 +195,9 @@ jobs:
|
||||
ls -R ./compiled-rn
|
||||
- name: Add REVISION files
|
||||
run: |
|
||||
echo ${{ github.event.workflow_run.head_sha }} >> ./compiled/facebook-www/REVISION
|
||||
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
|
||||
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
|
||||
echo ${{ github.event.workflow_run.head_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: |
|
||||
@@ -149,11 +210,11 @@ jobs:
|
||||
echo "current_version_classic=$VERSION_CLASSIC" >> "$GITHUB_OUTPUT"
|
||||
echo "current_version_modern=$VERSION_MODERN" >> "$GITHUB_OUTPUT"
|
||||
echo "current_version_rn=$VERSION_NATIVE_FB" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: compiled
|
||||
path: compiled/
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: compiled-rn
|
||||
path: compiled-rn/
|
||||
@@ -168,7 +229,7 @@ jobs:
|
||||
ref: builds/facebook-www
|
||||
- name: Ensure clean directory
|
||||
run: rm -rf compiled
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: compiled
|
||||
path: compiled/
|
||||
@@ -189,18 +250,14 @@ jobs:
|
||||
grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "No files found with $CURRENT_VERSION_MODERN"
|
||||
grep -rl "$CURRENT_VERSION_MODERN" ./compiled | xargs -r sed -i -e "s/$CURRENT_VERSION_MODERN/$LAST_VERSION_MODERN/g"
|
||||
grep -rl "$CURRENT_VERSION_MODERN" ./compiled || echo "Modern version reverted"
|
||||
- name: Check for changes
|
||||
- name: Check if only the REVISION file has changed
|
||||
id: check_should_commit
|
||||
run: |
|
||||
echo "Full git status"
|
||||
git add .
|
||||
git status
|
||||
echo "===================="
|
||||
if git status --porcelain | grep -qv '/REVISION'; then
|
||||
echo "Changes detected"
|
||||
echo "===== Changes ====="
|
||||
git --no-pager diff -U0 | grep '^[+-]' | head -n 50
|
||||
echo "==================="
|
||||
echo "should_commit=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No Changes detected"
|
||||
@@ -233,12 +290,12 @@ jobs:
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.workflow_run.head_commit.message }}
|
||||
${{ github.event.head_commit.message }}
|
||||
|
||||
DiffTrain build for [${{ github.event.workflow_run.head_sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }})
|
||||
DiffTrain build for [${{ github.sha }}](https://github.com/facebook/react/commit/${{ github.sha }})
|
||||
branch: builds/facebook-www
|
||||
commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
|
||||
commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
|
||||
commit_user_name: ${{ github.actor }}
|
||||
commit_user_email: ${{ github.actor }}@users.noreply.github.com
|
||||
create_branch: true
|
||||
|
||||
commit_fbsource_artifacts:
|
||||
@@ -251,7 +308,7 @@ jobs:
|
||||
ref: builds/facebook-fbsource
|
||||
- name: Ensure clean directory
|
||||
run: rm -rf compiled-rn
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: compiled-rn
|
||||
path: compiled-rn/
|
||||
@@ -265,19 +322,17 @@ jobs:
|
||||
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "No files found with $CURRENT_VERSION"
|
||||
grep -rl "$CURRENT_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$CURRENT_VERSION/$LAST_VERSION/g"
|
||||
grep -rl "$CURRENT_VERSION" ./compiled-rn || echo "Version reverted"
|
||||
- name: Check for changes
|
||||
- name: Check if only the REVISION file has changed
|
||||
id: check_should_commit
|
||||
run: |
|
||||
echo "Full git status"
|
||||
git add .
|
||||
git --no-pager diff -U0 --cached | grep '^[+-]' | head -n 100
|
||||
git status
|
||||
echo "===================="
|
||||
# Ignore REVISION or lines removing @generated headers.
|
||||
if git diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" > /dev/null; then
|
||||
echo "Checking for changes"
|
||||
# Check if there are changes in the files other than REVISION or @generated headers
|
||||
# We also filter out the file name lines with "---" and "+++".
|
||||
if git diff -- . ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|@generated SignedSource)" | grep "^[+-]" > /dev/null; then
|
||||
echo "Changes detected"
|
||||
echo "===== Changes ====="
|
||||
git --no-pager diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" | head -n 50
|
||||
echo "==================="
|
||||
echo "should_commit=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No Changes detected"
|
||||
@@ -293,108 +348,20 @@ jobs:
|
||||
grep -rl "$LAST_VERSION" ./compiled-rn || echo "No files found with $LAST_VERSION"
|
||||
grep -rl "$LAST_VERSION" ./compiled-rn | xargs -r sed -i -e "s/$LAST_VERSION/$CURRENT_VERSION/g"
|
||||
grep -rl "$LAST_VERSION" ./compiled-rn || echo "Version re-applied"
|
||||
- name: Add files for signing
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
echo ":"
|
||||
git add .
|
||||
- name: Signing files
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// TODO: Move this to a script file.
|
||||
// We currently can't call scripts from the repo because
|
||||
// at this point in the workflow, we're on the compiled
|
||||
// artifact branch (so the scripts don't exist).
|
||||
// We can fix this with a composite action in the main repo.
|
||||
// This script is duplicated above.
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
// TODO: when we move this to a script, we can use this from npm.
|
||||
// Copy of signedsource since we can't install deps on this branch.
|
||||
const GENERATED = '@' + 'generated';
|
||||
const NEWTOKEN = '<<SignedSource::*O*zOeWoEQle#+L!plEphiEmie@IsG>>';
|
||||
const PATTERN = new RegExp(`${GENERATED} (?:SignedSource<<([a-f0-9]{32})>>)`);
|
||||
|
||||
const TokenNotFoundError = new Error(
|
||||
`SignedSource.signFile(...): Cannot sign file without token: ${NEWTOKEN}`
|
||||
);
|
||||
|
||||
function hash(data, encoding) {
|
||||
const md5sum = crypto.createHash('md5');
|
||||
md5sum.update(data, encoding);
|
||||
return md5sum.digest('hex');
|
||||
}
|
||||
|
||||
const SignedSource = {
|
||||
getSigningToken() {
|
||||
return `${GENERATED} ${NEWTOKEN}`;
|
||||
},
|
||||
isSigned(data) {
|
||||
return PATTERN.exec(data) != null;
|
||||
},
|
||||
signFile(data) {
|
||||
if (!data.includes(NEWTOKEN)) {
|
||||
if (SignedSource.isSigned(data)) {
|
||||
// Signing a file that was previously signed.
|
||||
data = data.replace(PATTERN, SignedSource.getSigningToken());
|
||||
} else {
|
||||
throw TokenNotFoundError;
|
||||
}
|
||||
}
|
||||
return data.replace(NEWTOKEN, `SignedSource<<${hash(data, 'utf8')}>>`);
|
||||
},
|
||||
};
|
||||
|
||||
const directory = './compiled-rn';
|
||||
console.log('Signing files in directory:', directory);
|
||||
try {
|
||||
const result = execSync(`git status --porcelain ${directory}`, {encoding: 'utf8'});
|
||||
|
||||
// Parse the git status output to get file paths
|
||||
const files = result.split('\n').filter(file => file.endsWith('.js'));
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error(
|
||||
'git status returned no files to sign. this job should not have run.'
|
||||
);
|
||||
} else {
|
||||
files.forEach(line => {
|
||||
const file = line.slice(3).trim();
|
||||
if (file) {
|
||||
console.log(' Signing file:', file);
|
||||
const originalContents = fs.readFileSync(file, 'utf8');
|
||||
const signedContents = SignedSource.signFile(
|
||||
originalContents
|
||||
// Need to add the header in, since it's not inserted at build time.
|
||||
.replace(' */\n', ` * ${SignedSource.getSigningToken()}\n */\n`)
|
||||
);
|
||||
|
||||
fs.writeFileSync(file, signedContents, 'utf8');
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
process.exitCode = 1;
|
||||
console.error('Error signing files:', e);
|
||||
}
|
||||
- name: Will commit these changes
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git status
|
||||
echo ":"
|
||||
git status -u
|
||||
- name: Commit changes to branch
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.workflow_run.head_commit.message }}
|
||||
${{ github.event.head_commit.message }}
|
||||
|
||||
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}.
|
||||
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.sha }}.
|
||||
branch: builds/facebook-fbsource
|
||||
commit_user_name: ${{ github.event.workflow_run.triggering_actor.login }}
|
||||
commit_user_email: ${{ github.event.workflow_run.triggering_actor.email || format('{0}@users.noreply.github.com', github.event.workflow_run.triggering_actor.login) }}
|
||||
commit_user_name: ${{ github.actor }}
|
||||
commit_user_email: ${{ github.actor }}@users.noreply.github.com
|
||||
create_branch: true
|
||||
@@ -1,18 +1,13 @@
|
||||
name: (Compiler) Playground
|
||||
name: Compiler Playground
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- compiler/**
|
||||
- "compiler/**"
|
||||
- .github/workflows/compiler-playground.yml
|
||||
|
||||
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
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: compiler
|
||||
@@ -25,8 +20,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
@@ -1,4 +1,4 @@
|
||||
name: (Compiler) Rust
|
||||
name: React Compiler (Rust)
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -18,7 +18,6 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -Dwarnings
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -1,18 +1,13 @@
|
||||
name: (Compiler) TypeScript
|
||||
name: React Compiler (TypeScript)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- compiler/**
|
||||
- "compiler/**"
|
||||
- .github/workflows/compiler-typescript.yml
|
||||
|
||||
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
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: compiler
|
||||
@@ -28,6 +23,25 @@ jobs:
|
||||
- id: set-matrix
|
||||
run: echo "matrix=$(find packages -mindepth 1 -maxdepth 1 -type d | sed 's!packages/!!g' | tr '\n' ',' | sed s/.$// | jq -Rsc '. / "," - [""]')" >> $GITHUB_OUTPUT
|
||||
|
||||
# Hardcoded to improve parallelism for babel-plugin-react-compiler
|
||||
prettier:
|
||||
name: Run prettier
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('compiler/**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn prettier:ci
|
||||
|
||||
# Hardcoded to improve parallelism
|
||||
lint:
|
||||
name: Lint babel-plugin-react-compiler
|
||||
@@ -36,8 +50,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
@@ -55,8 +69,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
@@ -79,8 +93,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
node-version: 18.x
|
||||
cache: "yarn"
|
||||
cache-dependency-path: compiler/yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
205
.github/workflows/devtools_check_repro.yml
vendored
Normal file
205
.github/workflows/devtools_check_repro.yml
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
name: DevTools Check for bug repro
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
check-repro:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const URL_REGEXP = /### Website or app[\r\n]+([^#]+)###/m;
|
||||
const REPRO_STEPS_REGEXP = /### Repro steps[\r\n]+([^#]+)###/m;
|
||||
const LABEL_NEEDS_MORE_INFORMATION = "Resolution: Needs More Information";
|
||||
const LABEL_UNCONFIRMED = "Status: Unconfirmed";
|
||||
|
||||
function debug(...args) {
|
||||
core.info(args.map(JSON.stringify).join(' '));
|
||||
}
|
||||
|
||||
if (context.payload.comment) {
|
||||
debug('Ignoring comment update.');
|
||||
return;
|
||||
}
|
||||
|
||||
const user = context.payload.sender.login;
|
||||
const issue = context.payload.issue;
|
||||
const body = issue.body;
|
||||
|
||||
const urlMatch = body.match(URL_REGEXP);
|
||||
const reproStepsMatch = body.match(REPRO_STEPS_REGEXP);
|
||||
|
||||
const url = urlMatch !== null ? urlMatch[1].trim() : null;
|
||||
const reproSteps = reproStepsMatch !== null ? reproStepsMatch[1].trim() : null;
|
||||
|
||||
if (!url || !reproSteps) {
|
||||
debug('This issue is not a DevTools bug report.');
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`found URL "${url}"`);
|
||||
debug(`found repro steps "${reproSteps}"`);
|
||||
|
||||
async function createComment(comment) {
|
||||
// Format
|
||||
comment = comment
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.join("\n")
|
||||
.trim();
|
||||
|
||||
await github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment,
|
||||
});
|
||||
}
|
||||
|
||||
async function getGitHubActionComments() {
|
||||
debug(`Loading existing comments...`);
|
||||
|
||||
const comments = await github.issues.listComments({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
return comments.data.filter(comment => {
|
||||
debug(`comment by user: "${comment.user.login}"`);
|
||||
return comment.user.login === 'github-actions[bot]';
|
||||
});
|
||||
}
|
||||
|
||||
async function getIssueLabels() {
|
||||
const issues = await github.issues.listLabelsOnIssue({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
});
|
||||
|
||||
return issues.data;
|
||||
}
|
||||
|
||||
async function updateIssue(state, assignees = []) {
|
||||
await github.issues.update({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state,
|
||||
assignees,
|
||||
});
|
||||
}
|
||||
|
||||
async function closeWithComment(comment) {
|
||||
if (issue.state !== 'open') {
|
||||
debug(`Issue is not open`);
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = await getIssueLabels();
|
||||
const label = labels.find(label => label.name === LABEL_UNCONFIRMED);
|
||||
if (!label) {
|
||||
debug(`Issue was not opened via DevTools bug report template`);
|
||||
return;
|
||||
}
|
||||
|
||||
const comments = await getGitHubActionComments();
|
||||
if (comments.length > 0) {
|
||||
debug(`Already commented on issue; won't comment again`);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`Missing required information`);
|
||||
|
||||
await github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: [LABEL_NEEDS_MORE_INFORMATION],
|
||||
});
|
||||
|
||||
await createComment(comment);
|
||||
await updateIssue('closed', [user]);
|
||||
}
|
||||
|
||||
async function openWithComment(comment) {
|
||||
if (issue.state !== 'closed') {
|
||||
debug(`Issue is already open`);
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = await getIssueLabels();
|
||||
const label = labels.find(label => label.name === LABEL_NEEDS_MORE_INFORMATION);
|
||||
if (!label) {
|
||||
debug(`Issue was not tagged as needs information`);
|
||||
return;
|
||||
}
|
||||
|
||||
const comments = await getGitHubActionComments();
|
||||
if (comments.length === 0) {
|
||||
debug(`Issue was closed by someone else; won't reopen`);
|
||||
return;
|
||||
}
|
||||
|
||||
debug(`Re-opening closed issue`);
|
||||
|
||||
await github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: LABEL_NEEDS_MORE_INFORMATION,
|
||||
});
|
||||
|
||||
await createComment(comment);
|
||||
await updateIssue('open');
|
||||
}
|
||||
|
||||
const PROBABLY_NOT_A_URL_REGEX = /(^Chrome$|^Firefox$| Website)/i;
|
||||
|
||||
const COMMENT_HEADER = `
|
||||
@${user}: We're sorry you've seen this error. ❤️
|
||||
`.trim();
|
||||
|
||||
const COMMENT_FOOTER = `
|
||||
Please help us by providing a link to a CodeSandbox (https://codesandbox.io/s/new), a repository on GitHub, or a minimal code example that reproduces the problem. (Screenshots or videos can also be helpful if they help provide context on how to repro the bug.)
|
||||
|
||||
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
|
||||
|
||||
Issues without repros are automatically closed but we will re-open if you update with repro info.
|
||||
`.trim();
|
||||
|
||||
if (url.includes("localhost")) {
|
||||
closeWithComment(`
|
||||
${COMMENT_HEADER}
|
||||
|
||||
Unfortunately the URL you provided ("localhost") is not publicly accessible. (This means that we will not be able to reproduce the problem you're reporting.)
|
||||
|
||||
${COMMENT_FOOTER}
|
||||
`);
|
||||
} else if (url.length < 10 || url.match(PROBABLY_NOT_A_URL_REGEX)) {
|
||||
closeWithComment(`
|
||||
${COMMENT_HEADER}
|
||||
|
||||
It looks like you forgot to specify a valid URL. (This means that we will not be able to reproduce the problem you're reporting.)
|
||||
|
||||
${COMMENT_FOOTER}
|
||||
`);
|
||||
} else if (reproSteps.length < 25) {
|
||||
closeWithComment(`
|
||||
${COMMENT_HEADER}
|
||||
|
||||
Unfortunately, it doesn't look like this issue has enough info for one of us to reproduce and fix it though.
|
||||
|
||||
${COMMENT_FOOTER}
|
||||
`);
|
||||
} else {
|
||||
openWithComment(`
|
||||
Thank you for providing repro steps! Re-opening issue now for triage.
|
||||
`);
|
||||
}
|
||||
173
.github/workflows/devtools_regression_tests.yml
vendored
173
.github/workflows/devtools_regression_tests.yml
vendored
@@ -1,173 +0,0 @@
|
||||
name: (DevTools) Regression Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
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
|
||||
|
||||
jobs:
|
||||
download_build:
|
||||
name: Download base build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: scripts/release
|
||||
- name: Download react-devtools artifacts for base revision
|
||||
run: |
|
||||
git fetch origin main
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || '$(git rev-parse origin/main)' }}
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
|
||||
build_devtools_and_process_artifacts:
|
||||
name: Build DevTools and process artifacts
|
||||
needs: download_build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: build
|
||||
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive devtools build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools
|
||||
path: build/devtools.tgz
|
||||
# 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
|
||||
- name: Archive firefox extension
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools-firefox-extension
|
||||
path: build/devtools/firefox-extension.zip
|
||||
|
||||
run_devtools_tests_for_versions:
|
||||
name: Run DevTools tests for versions
|
||||
needs: build_devtools_and_process_artifacts
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore all archived build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: ./scripts/ci/download_devtools_regression_build.js ${{ matrix.version }} --replaceBuild
|
||||
- run: node ./scripts/jest/jest-cli.js --build --project devtools --release-channel=experimental --reactVersion ${{ matrix.version }} --ci
|
||||
|
||||
run_devtools_e2e_tests_for_versions:
|
||||
name: Run DevTools e2e tests for versions
|
||||
needs: build_devtools_and_process_artifacts
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- "16.0"
|
||||
- "16.5" # schedule package
|
||||
- "16.8" # hooks
|
||||
- "17.0"
|
||||
- "18.0"
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore all archived build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- 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 }}
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
- name: Cleanup build regression folder
|
||||
run: rm -r ./build-regression
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots
|
||||
path: ./tmp/screenshots
|
||||
@@ -1,19 +1,20 @@
|
||||
name: (Runtime) Fuzz tests
|
||||
|
||||
name: facebook/react/fuzz_tests
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 * * * *
|
||||
- cron: 0 * * * *
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
inputs:
|
||||
prerelease_commit_sha:
|
||||
required: false
|
||||
jobs:
|
||||
test_fuzz:
|
||||
if: inputs.prerelease_commit_sha == ''
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TZ: "/usr/share/zoneinfo/America/Los_Angeles"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: actions/setup-node@v4
|
||||
611
.github/workflows/runtime_build_and_test.yml
vendored
611
.github/workflows/runtime_build_and_test.yml
vendored
@@ -1,611 +0,0 @@
|
||||
name: (Runtime) Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- compiler/**
|
||||
|
||||
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
|
||||
|
||||
jobs:
|
||||
# ----- FLOW -----
|
||||
discover_flow_inline_configs:
|
||||
name: Discover flow inline configs
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.result }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/github-script@v7
|
||||
id: set-matrix
|
||||
with:
|
||||
script: |
|
||||
const inlinedHostConfigs = require('./scripts/shared/inlinedHostConfigs.js');
|
||||
return inlinedHostConfigs.map(config => config.shortName);
|
||||
|
||||
flow:
|
||||
name: Flow check ${{ matrix.flow_inline_config_shortname }}
|
||||
needs: discover_flow_inline_configs
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- 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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- 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)
|
||||
|
||||
# ----- FEATURE FLAGS -----
|
||||
flags:
|
||||
name: Check flags
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn flags
|
||||
|
||||
# ----- TESTS -----
|
||||
test:
|
||||
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
params:
|
||||
- "-r=stable --env=development"
|
||||
- "-r=stable --env=production"
|
||||
- "-r=experimental --env=development"
|
||||
- "-r=experimental --env=production"
|
||||
- "-r=www-classic --env=development --variant=false"
|
||||
- "-r=www-classic --env=production --variant=false"
|
||||
- "-r=www-classic --env=development --variant=true"
|
||||
- "-r=www-classic --env=production --variant=true"
|
||||
- "-r=www-modern --env=development --variant=false"
|
||||
- "-r=www-modern --env=production --variant=false"
|
||||
- "-r=www-modern --env=development --variant=true"
|
||||
- "-r=www-modern --env=production --variant=true"
|
||||
- "-r=xplat --env=development --variant=false"
|
||||
- "-r=xplat --env=development --variant=true"
|
||||
- "-r=xplat --env=production --variant=false"
|
||||
- "-r=xplat --env=production --variant=true"
|
||||
# TODO: Test more persistent configurations?
|
||||
- "-r=stable --env=development --persistent"
|
||||
- "-r=experimental --env=development --persistent"
|
||||
shard:
|
||||
- 1/5
|
||||
- 2/5
|
||||
- 3/5
|
||||
- 4/5
|
||||
- 5/5
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
|
||||
|
||||
# ----- BUILD -----
|
||||
build_and_lint:
|
||||
name: yarn build and lint
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
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]
|
||||
release_channel: [stable, experimental]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
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@v4
|
||||
id: node_modules
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
|
||||
env:
|
||||
CI: github
|
||||
RELEASE_CHANNEL: ${{ matrix.release_channel }}
|
||||
NODE_INDEX: ${{ matrix.worker_id }}
|
||||
- name: Lint build
|
||||
run: yarn lint-build
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
|
||||
path: build
|
||||
|
||||
test_build:
|
||||
name: yarn test-build
|
||||
needs: build_and_lint
|
||||
strategy:
|
||||
matrix:
|
||||
test_params: [
|
||||
# Intentionally passing these as strings instead of creating a
|
||||
# separate parameter per CLI argument, since it's easier to
|
||||
# control/see which combinations we want to run.
|
||||
-r=stable --env=development,
|
||||
-r=stable --env=production,
|
||||
-r=experimental --env=development,
|
||||
-r=experimental --env=production,
|
||||
|
||||
# Dev Tools
|
||||
--project=devtools -r=experimental,
|
||||
|
||||
# TODO: Update test config to support www build tests
|
||||
# - "-r=www-classic --env=development --variant=false"
|
||||
# - "-r=www-classic --env=production --variant=false"
|
||||
# - "-r=www-classic --env=development --variant=true"
|
||||
# - "-r=www-classic --env=production --variant=true"
|
||||
# - "-r=www-modern --env=development --variant=false"
|
||||
# - "-r=www-modern --env=production --variant=false"
|
||||
# - "-r=www-modern --env=development --variant=true"
|
||||
# - "-r=www-modern --env=production --variant=true"
|
||||
|
||||
# TODO: Update test config to support xplat build tests
|
||||
# - "-r=xplat --env=development --variant=false"
|
||||
# - "-r=xplat --env=development --variant=true"
|
||||
# - "-r=xplat --env=production --variant=false"
|
||||
# - "-r=xplat --env=production --variant=true"
|
||||
|
||||
# TODO: Test more persistent configurations?
|
||||
]
|
||||
shard:
|
||||
- 1/3
|
||||
- 2/3
|
||||
- 3/3
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci
|
||||
|
||||
process_artifacts_combined:
|
||||
name: Process artifacts combined
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
|
||||
- name: Scrape warning messages
|
||||
run: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
# Compress build directory into a single tarball for easy download
|
||||
- run: tar -zcvf ./build.tgz ./build
|
||||
# TODO: Migrate scripts to use `build` directory instead of `build2`
|
||||
- run: cp ./build.tgz ./build2.tgz
|
||||
- name: Archive build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifacts_combined
|
||||
path: |
|
||||
./build.tgz
|
||||
./build2.tgz
|
||||
|
||||
check_error_codes:
|
||||
name: Search build artifacts for unminified errors
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Search build artifacts for unminified errors
|
||||
run: |
|
||||
yarn extract-errors
|
||||
git diff --quiet || (echo "Found unminified errors. Either update the error codes map or disable error minification for the affected build, if appropriate." && false)
|
||||
|
||||
check_release_dependencies:
|
||||
name: Check release dependencies
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: yarn check-release-dependencies
|
||||
|
||||
RELEASE_CHANNEL_stable_yarn_test_dom_fixtures:
|
||||
name: Check fixtures DOM (stable)
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: v2-yarn_cache_fixtures_dom-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
working-directory: fixtures/dom
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Run DOM fixture tests
|
||||
run: |
|
||||
yarn predev
|
||||
yarn test
|
||||
working-directory: fixtures/dom
|
||||
env:
|
||||
RELEASE_CHANNEL: stable
|
||||
|
||||
# ----- FLIGHT -----
|
||||
run_fixtures_flight_tests:
|
||||
name: Run fixtures Flight tests
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
# Fixture copies some built packages from the workroot after install.
|
||||
# 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
|
||||
id: node_modules
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: v2-yarn_cache_fixtures_flight-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
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
|
||||
sudo npx playwright install-deps
|
||||
- name: Run tests
|
||||
working-directory: fixtures/flight
|
||||
run: yarn test
|
||||
env:
|
||||
# Otherwise the webserver is a blackbox
|
||||
DEBUG: pw:webserver
|
||||
- name: Archive Flight fixture artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: flight-playwright-report
|
||||
path: fixtures/flight/playwright-report
|
||||
- name: Archive Flight fixture artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: flight-test-results
|
||||
path: fixtures/flight/test-results
|
||||
|
||||
# ----- DEVTOOLS -----
|
||||
build_devtools_and_process_artifacts:
|
||||
name: Build DevTools and process artifacts
|
||||
needs: build_and_lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Archive devtools build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools
|
||||
path: build/devtools.tgz
|
||||
# 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
|
||||
- name: Archive firefox extension
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: react-devtools-firefox-extension
|
||||
path: build/devtools/firefox-extension.zip
|
||||
|
||||
run_devtools_e2e_tests:
|
||||
name: Run DevTools e2e tests
|
||||
needs: build_devtools_and_process_artifacts
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- run: |
|
||||
npx playwright install
|
||||
sudo npx playwright install-deps
|
||||
- run: ./scripts/ci/run_devtools_e2e_tests.js
|
||||
env:
|
||||
RELEASE_CHANNEL: experimental
|
||||
|
||||
# ----- SIZEBOT -----
|
||||
download_base_build_for_sizebot:
|
||||
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' }}
|
||||
name: Download base build for sizebot
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: scripts/release
|
||||
- name: Download artifacts for base revision
|
||||
run: |
|
||||
git fetch origin main
|
||||
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse origin/main)
|
||||
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
|
||||
run: rm -rf ./base-build/node_modules
|
||||
- name: Display structure of base-build
|
||||
run: ls -R base-build
|
||||
- name: Archive base-build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: base-build
|
||||
path: base-build
|
||||
|
||||
sizebot:
|
||||
name: Run sizebot
|
||||
needs: [build_and_lint, download_base_build_for_sizebot]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- name: Restore archived build for PR
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Scrape warning messages
|
||||
run: |
|
||||
mkdir -p ./build/__test_utils__
|
||||
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
|
||||
- name: Display structure of build for PR
|
||||
run: ls -R build
|
||||
- name: Restore archived base-build from origin/main
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: base-build
|
||||
path: base-build
|
||||
- name: Display structure of base-build from origin/main
|
||||
run: ls -R base-build
|
||||
- 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
|
||||
50
.github/workflows/runtime_prereleases.yml
vendored
50
.github/workflows/runtime_prereleases.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: (Runtime) Publish Prereleases
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
commit_sha:
|
||||
required: true
|
||||
default: ''
|
||||
type: string
|
||||
release_channel:
|
||||
required: true
|
||||
type: string
|
||||
dist_tag:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
NPM_TOKEN:
|
||||
required: true
|
||||
|
||||
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
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
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: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn install --frozen-lockfile
|
||||
working-directory: scripts/release
|
||||
- run: |
|
||||
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 }}
|
||||
47
.github/workflows/runtime_prereleases_manual.yml
vendored
47
.github/workflows/runtime_prereleases_manual.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: (Runtime) Publish Prereleases Manual
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
prerelease_commit_sha:
|
||||
required: true
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
|
||||
publish_prerelease_canary:
|
||||
name: Publish to Canary channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
with:
|
||||
commit_sha: ${{ inputs.prerelease_commit_sha }}
|
||||
release_channel: stable
|
||||
# The tags to use when publishing canaries. The main one we should
|
||||
# always include is "canary" but we can use multiple (e.g. alpha,
|
||||
# beta, rc). To declare multiple, use a comma-separated string, like
|
||||
# this:
|
||||
# dist_tag: "canary,alpha,beta,rc"
|
||||
#
|
||||
# TODO: We currently tag canaries with "next" in addition to "canary"
|
||||
# because this used to be called the "next" channel and some
|
||||
# downstream consumers might still expect that tag. We can remove this
|
||||
# after some time has elapsed and the change has been communicated.
|
||||
dist_tag: canary,next,rc
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish_prerelease_experimental:
|
||||
name: Publish to Experimental channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
# 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
|
||||
# dist tags.
|
||||
needs: publish_prerelease_canary
|
||||
with:
|
||||
commit_sha: ${{ inputs.prerelease_commit_sha }}
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
@@ -1,35 +0,0 @@
|
||||
name: (Runtime) Publish Prereleases Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# At 10 minutes past 16:00 on Mon, Tue, Wed, Thu, and Fri
|
||||
- cron: 10 16 * * 1,2,3,4,5
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
publish_prerelease_canary:
|
||||
name: Publish to Canary channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: stable
|
||||
dist_tag: canary,next,rc
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
publish_prerelease_experimental:
|
||||
name: Publish to Experimental channel
|
||||
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
|
||||
# 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
|
||||
# dist tags.
|
||||
needs: publish_prerelease_canary
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
release_channel: experimental
|
||||
dist_tag: experimental
|
||||
secrets:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
84
.github/workflows/shared_lint.yml
vendored
84
.github/workflows/shared_lint.yml
vendored
@@ -1,84 +0,0 @@
|
||||
name: (Shared) Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
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
|
||||
|
||||
jobs:
|
||||
prettier:
|
||||
name: Run prettier
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn prettier-check
|
||||
|
||||
eslint:
|
||||
name: Run eslint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: node ./scripts/tasks/eslint
|
||||
|
||||
check_license:
|
||||
name: Check license
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: ./scripts/ci/check_license.sh
|
||||
|
||||
test_print_warnings:
|
||||
name: Test print warnings
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Restore cached node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: ./scripts/ci/test_print_warnings.sh
|
||||
@@ -1,13 +1,9 @@
|
||||
# Configuration for stale action workflow - https://github.com/actions/stale
|
||||
name: (Shared) Manage stale issues and PRs
|
||||
name: 'Manage stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
# Run hourly
|
||||
- cron: '0 * * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
@@ -1,6 +1,6 @@
|
||||
# react runtime
|
||||
build
|
||||
|
||||
compiler
|
||||
packages/react-devtools-core/dist
|
||||
packages/react-devtools-extensions/chrome/build
|
||||
packages/react-devtools-extensions/firefox/build
|
||||
@@ -14,27 +14,3 @@ packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
|
||||
packages/react-devtools-shell/dist
|
||||
packages/react-devtools-timeline/dist
|
||||
packages/react-devtools-timeline/static
|
||||
|
||||
# react compiler
|
||||
compiler/**/dist
|
||||
compiler/**/__tests__/fixtures/**/*.expect.md
|
||||
compiler/**/.next
|
||||
|
||||
# contains invalid graphql`...` which results in a promise rejection error from `yarn prettier-all`.
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
|
||||
|
||||
compiler/crates
|
||||
compiler/apps/playground/public
|
||||
|
||||
compiler/**/LICENSE
|
||||
compiler/*.md*
|
||||
compiler/*.json
|
||||
compiler/*.css
|
||||
compiler/*.webmanifest
|
||||
compiler/*.map
|
||||
compiler/*.sh
|
||||
compiler/*.txt
|
||||
compiler/*.ico
|
||||
compiler/*.svg
|
||||
compiler/*.lock
|
||||
compiler/*.toml
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion');
|
||||
const {
|
||||
esNextPaths,
|
||||
typescriptPaths,
|
||||
} = require('./scripts/shared/pathsByLanguageVersion');
|
||||
|
||||
module.exports = {
|
||||
plugins: ['prettier-plugin-hermes-parser'],
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
bracketSameLine: true,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 80,
|
||||
parser: 'hermes',
|
||||
parser: 'flow',
|
||||
arrowParens: 'avoid',
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.code-workspace'],
|
||||
options: {
|
||||
parser: 'json-stringify',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: esNextPaths,
|
||||
options: {
|
||||
@@ -25,7 +21,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
files: typescriptPaths,
|
||||
options: {
|
||||
trailingComma: 'all',
|
||||
parser: 'typescript',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# [React](https://react.dev/) · [](https://github.com/facebook/react/blob/main/LICENSE) [](https://www.npmjs.com/package/react) [](https://github.com/facebook/react/actions/workflows/runtime_build_and_test.yml) [](https://github.com/facebook/react/actions/workflows/compiler_typescript.yml) [](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
||||
# [React](https://react.dev/) · [](https://github.com/facebook/react/blob/main/LICENSE) [](https://www.npmjs.com/package/react) [](https://circleci.com/gh/facebook/react) [](https://legacy.reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
||||
|
||||
React is a JavaScript library for building user interfaces.
|
||||
|
||||
@@ -18,7 +18,7 @@ React has been designed for gradual adoption from the start, and **you can use a
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the React documentation [on the website](https://react.dev/).
|
||||
You can find the React documentation [on the website](https://react.dev/).
|
||||
|
||||
Check out the [Getting Started](https://react.dev/learn) page for a quick overview.
|
||||
|
||||
@@ -55,7 +55,7 @@ root.render(<HelloMessage name="Taylor" />);
|
||||
|
||||
This example will render "Hello Taylor" into a container on the page.
|
||||
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://react.dev/learn#writing-markup-with-jsx). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML.
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://react.dev/learn#writing-markup-with-jsx). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const ReactVersion = '19.0.0';
|
||||
// choose to change it to "alpha", "beta", "rc", etc.
|
||||
//
|
||||
// It only affects the label used in the version string. To customize the
|
||||
// npm dist tags used during publish, refer to .github/workflows/runtime_prereleases_*.yml.
|
||||
// npm dist tags used during publish, refer to .circleci/config.yml.
|
||||
const canaryChannelLabel = 'rc';
|
||||
|
||||
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
|
||||
|
||||
@@ -43,12 +43,6 @@ module.exports = {
|
||||
|
||||
"multiline-comment-style": ["error", "starred-block"],
|
||||
|
||||
/**
|
||||
* We sometimes need to check for control characters in regexes for things like preserving input
|
||||
* strings
|
||||
*/
|
||||
"no-control-regex": "off",
|
||||
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
|
||||
/*
|
||||
@@ -88,7 +82,7 @@ module.exports = {
|
||||
],
|
||||
"@typescript-eslint/array-type": ["error", { default: "generic" }],
|
||||
"@typescript-eslint/triple-slash-reference": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
|
||||
21
compiler/.prettierignore
Normal file
21
compiler/.prettierignore
Normal file
@@ -0,0 +1,21 @@
|
||||
**/dist
|
||||
**/__tests__/fixtures/**/*.expect.md
|
||||
**/__tests__/fixtures/**/*.flow.js
|
||||
**/.next
|
||||
|
||||
crates
|
||||
apps/playground/public
|
||||
|
||||
**/LICENSE
|
||||
.*
|
||||
*.md*
|
||||
*.json
|
||||
*.css
|
||||
*.webmanifest
|
||||
*.map
|
||||
*.sh
|
||||
*.txt
|
||||
*.ico
|
||||
*.svg
|
||||
*.lock
|
||||
*.toml
|
||||
9
compiler/.prettierrc.js
Normal file
9
compiler/.prettierrc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const config = {
|
||||
requirePragma: false,
|
||||
parser: "babel-ts",
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
trailingComma: "es5"
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
8
compiler/.vscode/settings.json
vendored
Normal file
8
compiler/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"[typescript][typescriptreact]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {encodeStore, type Store} from '../../lib/stores';
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { encodeStore, type Store } from "../../lib/stores";
|
||||
|
||||
const STORE: Store = {
|
||||
source: `export default function TestComponent({ x }) {
|
||||
@@ -17,33 +17,33 @@ const STORE: Store = {
|
||||
const HASH = encodeStore(STORE);
|
||||
|
||||
function concat(data: Array<string>): string {
|
||||
return data.join('');
|
||||
return data.join("");
|
||||
}
|
||||
|
||||
test('editor should compile successfully', async ({page}) => {
|
||||
await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
|
||||
test("editor should compile successfully", async ({ page }) => {
|
||||
await page.goto(`/#${HASH}`, { waitUntil: "networkidle" });
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/00-on-networkidle.png',
|
||||
path: "test-results/00-on-networkidle.png",
|
||||
});
|
||||
|
||||
// User input from hash compiles
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/01-show-js-before.png',
|
||||
path: "test-results/01-show-js-before.png",
|
||||
});
|
||||
const userInput =
|
||||
(await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
|
||||
expect(concat(userInput)).toMatchSnapshot('user-input.txt');
|
||||
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
|
||||
expect(concat(userInput)).toMatchSnapshot("user-input.txt");
|
||||
|
||||
// Reset button works
|
||||
page.on('dialog', dialog => dialog.accept());
|
||||
await page.getByRole('button', {name: 'Reset'}).click();
|
||||
page.on("dialog", (dialog) => dialog.accept());
|
||||
await page.getByRole("button", { name: "Reset" }).click();
|
||||
await page.screenshot({
|
||||
fullPage: true,
|
||||
path: 'test-results/02-show-js-after.png',
|
||||
path: "test-results/02-show-js-after.png",
|
||||
});
|
||||
const defaultInput =
|
||||
(await page.locator('.monaco-editor').nth(2).allInnerTexts()) ?? [];
|
||||
expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
|
||||
(await page.locator(".monaco-editor").nth(2).allInnerTexts()) ?? [];
|
||||
expect(concat(defaultInput)).toMatchSnapshot("default-input.txt");
|
||||
});
|
||||
|
||||
@@ -5,24 +5,25 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {NextPage} from 'next';
|
||||
import Head from 'next/head';
|
||||
import {SnackbarProvider} from 'notistack';
|
||||
import {Editor, Header, StoreProvider} from '../components';
|
||||
import MessageSnackbar from '../components/Message';
|
||||
import type { NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import { Editor, Header, StoreProvider } from "../components";
|
||||
import MessageSnackbar from "../components/Message";
|
||||
|
||||
const Home: NextPage = () => {
|
||||
return (
|
||||
<div className="flex flex-col w-screen h-screen font-light">
|
||||
<Head>
|
||||
<title>
|
||||
{process.env.NODE_ENV === 'development'
|
||||
? '[DEV] React Compiler Playground'
|
||||
: 'React Compiler Playground'}
|
||||
{process.env.NODE_ENV === "development"
|
||||
? "[DEV] React Compiler Playground"
|
||||
: "React Compiler Playground"}
|
||||
</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"></meta>
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
|
||||
></meta>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link
|
||||
@@ -44,7 +45,8 @@ const Home: NextPage = () => {
|
||||
<SnackbarProvider
|
||||
preventDuplicate
|
||||
maxSnack={10}
|
||||
Components={{message: MessageSnackbar}}>
|
||||
Components={{ message: MessageSnackbar }}
|
||||
>
|
||||
<Header />
|
||||
<Editor />
|
||||
</SnackbarProvider>
|
||||
|
||||
@@ -5,21 +5,26 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import '../styles/globals.css';
|
||||
import "../styles/globals.css";
|
||||
|
||||
export default function RootLayout({children}: {children: React.ReactNode}) {
|
||||
'use no memo';
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
"use no memo";
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>
|
||||
{process.env.NODE_ENV === 'development'
|
||||
? '[DEV] React Compiler Playground'
|
||||
: 'React Compiler Playground'}
|
||||
{process.env.NODE_ENV === "development"
|
||||
? "[DEV] React Compiler Playground"
|
||||
: "React Compiler Playground"}
|
||||
</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"></meta>
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
|
||||
></meta>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import {SnackbarProvider} from 'notistack';
|
||||
import {Editor, Header, StoreProvider} from '../components';
|
||||
import MessageSnackbar from '../components/Message';
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import { Editor, Header, StoreProvider } from "../components";
|
||||
import MessageSnackbar from "../components/Message";
|
||||
|
||||
export default function Hoot() {
|
||||
return (
|
||||
@@ -17,7 +17,8 @@ export default function Hoot() {
|
||||
<SnackbarProvider
|
||||
preventDuplicate
|
||||
maxSnack={10}
|
||||
Components={{message: MessageSnackbar}}>
|
||||
Components={{ message: MessageSnackbar }}
|
||||
>
|
||||
<Header />
|
||||
<Editor />
|
||||
</SnackbarProvider>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['next/babel'],
|
||||
presets: ["next/babel"],
|
||||
plugins: [
|
||||
[
|
||||
'babel-plugin-react-compiler',
|
||||
"babel-plugin-react-compiler",
|
||||
{
|
||||
runtimeModule: 'react-compiler-runtime',
|
||||
runtimeModule: "react-compiler-runtime",
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
@@ -11,86 +11,86 @@
|
||||
|
||||
module.exports = {
|
||||
// Text colors
|
||||
primary: '#23272F', // gray-90
|
||||
'primary-dark': '#F6F7F9', // gray-5
|
||||
secondary: '#404756', // gray-70
|
||||
'secondary-dark': '#EBECF0', // gray-10
|
||||
link: '#087EA4', // blue-50
|
||||
'link-dark': '#149ECA', // blue-40
|
||||
syntax: '#EBECF0', // gray-10
|
||||
wash: '#FFFFFF',
|
||||
'wash-dark': '#23272F', // gray-90
|
||||
card: '#F6F7F9', // gray-05
|
||||
'card-dark': '#343A46', // gray-80
|
||||
highlight: '#E6F7FF', // blue-10
|
||||
'highlight-dark': 'rgba(88,175,223,.1)',
|
||||
border: '#EBECF0', // gray-10
|
||||
'border-dark': '#343A46', // gray-80
|
||||
'secondary-button': '#EBECF0', // gray-10
|
||||
'secondary-button-dark': '#404756', // gray-70
|
||||
primary: "#23272F", // gray-90
|
||||
"primary-dark": "#F6F7F9", // gray-5
|
||||
secondary: "#404756", // gray-70
|
||||
"secondary-dark": "#EBECF0", // gray-10
|
||||
link: "#087EA4", // blue-50
|
||||
"link-dark": "#149ECA", // blue-40
|
||||
syntax: "#EBECF0", // gray-10
|
||||
wash: "#FFFFFF",
|
||||
"wash-dark": "#23272F", // gray-90
|
||||
card: "#F6F7F9", // gray-05
|
||||
"card-dark": "#343A46", // gray-80
|
||||
highlight: "#E6F7FF", // blue-10
|
||||
"highlight-dark": "rgba(88,175,223,.1)",
|
||||
border: "#EBECF0", // gray-10
|
||||
"border-dark": "#343A46", // gray-80
|
||||
"secondary-button": "#EBECF0", // gray-10
|
||||
"secondary-button-dark": "#404756", // gray-70
|
||||
|
||||
// Gray
|
||||
'gray-95': '#16181D',
|
||||
'gray-90': '#23272F',
|
||||
'gray-80': '#343A46',
|
||||
'gray-70': '#404756',
|
||||
'gray-60': '#4E5769',
|
||||
'gray-50': '#5E687E', // unused
|
||||
'gray-40': '#78839B',
|
||||
'gray-30': '#99A1B3',
|
||||
'gray-20': '#BCC1CD',
|
||||
'gray-10': '#EBECF0',
|
||||
'gray-5': '#F6F7F9',
|
||||
"gray-95": "#16181D",
|
||||
"gray-90": "#23272F",
|
||||
"gray-80": "#343A46",
|
||||
"gray-70": "#404756",
|
||||
"gray-60": "#4E5769",
|
||||
"gray-50": "#5E687E", // unused
|
||||
"gray-40": "#78839B",
|
||||
"gray-30": "#99A1B3",
|
||||
"gray-20": "#BCC1CD",
|
||||
"gray-10": "#EBECF0",
|
||||
"gray-5": "#F6F7F9",
|
||||
|
||||
// Blue
|
||||
'blue-60': '#045975',
|
||||
'blue-50': '#087EA4',
|
||||
'blue-40': '#149ECA', // Brand Blue
|
||||
'blue-30': '#58C4DC', // unused
|
||||
'blue-20': '#ABE2ED',
|
||||
'blue-10': '#E6F7FF', // todo: doesn't match illustrations
|
||||
'blue-5': '#E6F6FA',
|
||||
"blue-60": "#045975",
|
||||
"blue-50": "#087EA4",
|
||||
"blue-40": "#149ECA", // Brand Blue
|
||||
"blue-30": "#58C4DC", // unused
|
||||
"blue-20": "#ABE2ED",
|
||||
"blue-10": "#E6F7FF", // todo: doesn't match illustrations
|
||||
"blue-5": "#E6F6FA",
|
||||
|
||||
// Yellow
|
||||
'yellow-60': '#B65700',
|
||||
'yellow-50': '#C76A15',
|
||||
'yellow-40': '#DB7D27', // unused
|
||||
'yellow-30': '#FABD62', // unused
|
||||
'yellow-20': '#FCDEB0', // unused
|
||||
'yellow-10': '#FDE7C7',
|
||||
'yellow-5': '#FEF5E7',
|
||||
"yellow-60": "#B65700",
|
||||
"yellow-50": "#C76A15",
|
||||
"yellow-40": "#DB7D27", // unused
|
||||
"yellow-30": "#FABD62", // unused
|
||||
"yellow-20": "#FCDEB0", // unused
|
||||
"yellow-10": "#FDE7C7",
|
||||
"yellow-5": "#FEF5E7",
|
||||
|
||||
// Purple
|
||||
'purple-60': '#2B3491', // unused
|
||||
'purple-50': '#575FB7',
|
||||
'purple-40': '#6B75DB',
|
||||
'purple-30': '#8891EC',
|
||||
'purple-20': '#C3C8F5', // unused
|
||||
'purple-10': '#E7E9FB',
|
||||
'purple-5': '#F3F4FD',
|
||||
"purple-60": "#2B3491", // unused
|
||||
"purple-50": "#575FB7",
|
||||
"purple-40": "#6B75DB",
|
||||
"purple-30": "#8891EC",
|
||||
"purple-20": "#C3C8F5", // unused
|
||||
"purple-10": "#E7E9FB",
|
||||
"purple-5": "#F3F4FD",
|
||||
|
||||
// Green
|
||||
'green-60': '#2B6E62',
|
||||
'green-50': '#388F7F',
|
||||
'green-40': '#44AC99',
|
||||
'green-30': '#7FCCBF',
|
||||
'green-20': '#ABDED5',
|
||||
'green-10': '#E5F5F2',
|
||||
'green-5': '#F4FBF9',
|
||||
"green-60": "#2B6E62",
|
||||
"green-50": "#388F7F",
|
||||
"green-40": "#44AC99",
|
||||
"green-30": "#7FCCBF",
|
||||
"green-20": "#ABDED5",
|
||||
"green-10": "#E5F5F2",
|
||||
"green-5": "#F4FBF9",
|
||||
|
||||
// RED
|
||||
'red-60': '#712D28',
|
||||
'red-50': '#A6423A', // unused
|
||||
'red-40': '#C1554D',
|
||||
'red-30': '#D07D77',
|
||||
'red-20': '#E5B7B3', // unused
|
||||
'red-10': '#F2DBD9', // unused
|
||||
'red-5': '#FAF1F0',
|
||||
"red-60": "#712D28",
|
||||
"red-50": "#A6423A", // unused
|
||||
"red-40": "#C1554D",
|
||||
"red-30": "#D07D77",
|
||||
"red-20": "#E5B7B3", // unused
|
||||
"red-10": "#F2DBD9", // unused
|
||||
"red-5": "#FAF1F0",
|
||||
|
||||
// MISC
|
||||
'code-block': '#99a1b30f', // gray-30 @ 6%
|
||||
'gradient-blue': '#58C4DC', // Only used for the landing gradient for now.
|
||||
"code-block": "#99a1b30f", // gray-30 @ 6%
|
||||
"gradient-blue": "#58C4DC", // Only used for the landing gradient for now.
|
||||
github: {
|
||||
highlight: '#fffbdd',
|
||||
highlight: "#fffbdd",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {parse as babelParse, ParserPlugin} from '@babel/parser';
|
||||
import * as HermesParser from 'hermes-parser';
|
||||
import traverse, {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import { parse, ParserPlugin } from "@babel/parser";
|
||||
import traverse, { NodePath } from "@babel/traverse";
|
||||
import * as t from "@babel/types";
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
@@ -20,51 +19,31 @@ import {
|
||||
run,
|
||||
ValueKind,
|
||||
type Hook,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment';
|
||||
import clsx from 'clsx';
|
||||
import invariant from 'invariant';
|
||||
import {useSnackbar} from 'notistack';
|
||||
import {useDeferredValue, useMemo} from 'react';
|
||||
import {useMountEffect} from '../../hooks';
|
||||
import {defaultStore} from '../../lib/defaultStore';
|
||||
} from "babel-plugin-react-compiler/src";
|
||||
import { type ReactFunctionType } from "babel-plugin-react-compiler/src/HIR/Environment";
|
||||
import clsx from "clsx";
|
||||
import invariant from "invariant";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useDeferredValue, useMemo } from "react";
|
||||
import { useMountEffect } from "../../hooks";
|
||||
import { defaultStore } from "../../lib/defaultStore";
|
||||
import {
|
||||
createMessage,
|
||||
initStoreFromUrlOrLocalStorage,
|
||||
MessageLevel,
|
||||
MessageSource,
|
||||
type Store,
|
||||
} from '../../lib/stores';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import Input from './Input';
|
||||
} from "../../lib/stores";
|
||||
import { useStore, useStoreDispatch } from "../StoreContext";
|
||||
import Input from "./Input";
|
||||
import {
|
||||
CompilerOutput,
|
||||
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';
|
||||
|
||||
function parseInput(input: string, language: 'flow' | 'typescript') {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
if (language === 'flow') {
|
||||
return HermesParser.parse(input, {
|
||||
babel: true,
|
||||
flow: 'all',
|
||||
sourceType: 'module',
|
||||
enableExperimentalComponentSyntax: true,
|
||||
});
|
||||
} else {
|
||||
return babelParse(input, {
|
||||
plugins: ['typescript', 'jsx'],
|
||||
sourceType: 'module',
|
||||
});
|
||||
}
|
||||
}
|
||||
} from "./Output";
|
||||
|
||||
function parseFunctions(
|
||||
source: string,
|
||||
language: 'flow' | 'typescript',
|
||||
source: string
|
||||
): Array<
|
||||
NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
@@ -76,7 +55,20 @@ function parseFunctions(
|
||||
>
|
||||
> = [];
|
||||
try {
|
||||
const ast = parseInput(source, language);
|
||||
const isFlow = source
|
||||
.trim()
|
||||
.split("\n", 1)[0]
|
||||
.match(/\s*\/\/\s*\@flow\s*/);
|
||||
let type_transform: ParserPlugin;
|
||||
if (isFlow) {
|
||||
type_transform = "flow";
|
||||
} else {
|
||||
type_transform = "typescript";
|
||||
}
|
||||
const ast = parse(source, {
|
||||
plugins: [type_transform, "jsx"],
|
||||
sourceType: "module",
|
||||
});
|
||||
traverse(ast, {
|
||||
FunctionDeclaration(nodePath) {
|
||||
items.push(nodePath);
|
||||
@@ -105,7 +97,7 @@ function parseFunctions(
|
||||
|
||||
const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
[
|
||||
'useFragment',
|
||||
"useFragment",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -114,7 +106,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'usePaginationFragment',
|
||||
"usePaginationFragment",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -123,7 +115,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'useRefetchableFragment',
|
||||
"useRefetchableFragment",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -132,7 +124,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'useLazyLoadQuery',
|
||||
"useLazyLoadQuery",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -141,7 +133,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
'usePreloadedQuery',
|
||||
"usePreloadedQuery",
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -156,22 +148,22 @@ function isHookName(s: string): boolean {
|
||||
}
|
||||
|
||||
function getReactFunctionType(
|
||||
id: NodePath<t.Identifier | null | undefined>,
|
||||
id: NodePath<t.Identifier | null | undefined>
|
||||
): ReactFunctionType {
|
||||
if (id && id.node && id.isIdentifier()) {
|
||||
if (isHookName(id.node.name)) {
|
||||
return 'Hook';
|
||||
return "Hook";
|
||||
}
|
||||
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
if (isPascalCaseNameSpace.test(id.node.name)) {
|
||||
return 'Component';
|
||||
return "Component";
|
||||
}
|
||||
}
|
||||
return 'Other';
|
||||
return "Other";
|
||||
}
|
||||
|
||||
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
function compile(source: string): CompilerOutput {
|
||||
const results = new Map<string, PrintedCompilerPipelineValue[]>();
|
||||
const error = new CompilerError();
|
||||
const upsert = (result: PrintedCompilerPipelineValue) => {
|
||||
@@ -182,33 +174,27 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
results.set(result.name, [result]);
|
||||
}
|
||||
};
|
||||
let language: 'flow' | 'typescript';
|
||||
if (source.match(/\@flow/)) {
|
||||
language = 'flow';
|
||||
} else {
|
||||
language = 'typescript';
|
||||
}
|
||||
try {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
const pragma = source.substring(0, source.indexOf('\n'));
|
||||
const pragma = source.substring(0, source.indexOf("\n"));
|
||||
const config = parseConfigPragma(pragma);
|
||||
|
||||
for (const fn of parseFunctions(source, language)) {
|
||||
for (const fn of parseFunctions(source)) {
|
||||
if (!fn.isFunctionDeclaration()) {
|
||||
error.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
reason: `Unexpected function type ${fn.node.type}`,
|
||||
description:
|
||||
'Playground only supports parsing function declarations',
|
||||
"Playground only supports parsing function declarations",
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: fn.node.loc ?? null,
|
||||
suggestions: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = fn.get('id');
|
||||
const id = fn.get("id");
|
||||
for (const result of run(
|
||||
fn,
|
||||
{
|
||||
@@ -216,20 +202,20 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
customHooks: new Map([...COMMON_HOOKS]),
|
||||
},
|
||||
getReactFunctionType(id),
|
||||
'_c',
|
||||
null,
|
||||
"_c",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)) {
|
||||
const fnName = fn.node.id?.name ?? null;
|
||||
switch (result.kind) {
|
||||
case 'ast': {
|
||||
case "ast": {
|
||||
upsert({
|
||||
kind: 'ast',
|
||||
kind: "ast",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: {
|
||||
type: 'FunctionDeclaration',
|
||||
type: "FunctionDeclaration",
|
||||
id: result.value.id,
|
||||
async: result.value.async,
|
||||
generator: result.value.generator,
|
||||
@@ -239,27 +225,27 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
case "hir": {
|
||||
upsert({
|
||||
kind: 'hir',
|
||||
kind: "hir",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printFunctionWithOutlined(result.value),
|
||||
value: printHIR(result.value.body),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
case "reactive": {
|
||||
upsert({
|
||||
kind: 'reactive',
|
||||
kind: "reactive",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
value: printReactiveFunction(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
case "debug": {
|
||||
upsert({
|
||||
kind: 'debug',
|
||||
kind: "debug",
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
@@ -288,24 +274,24 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
|
||||
reason: `Unexpected failure when transforming input! ${err}`,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
if (error.hasErrors()) {
|
||||
return [{kind: 'err', results, error: error}, language];
|
||||
return { kind: "err", results, error: error };
|
||||
}
|
||||
return [{kind: 'ok', results}, language];
|
||||
return { kind: "ok", results };
|
||||
}
|
||||
|
||||
export default function Editor() {
|
||||
const store = useStore();
|
||||
const deferredStore = useDeferredValue(store);
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const {enqueueSnackbar} = useSnackbar();
|
||||
const [compilerOutput, language] = useMemo(
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const compilerOutput = useMemo(
|
||||
() => compile(deferredStore.source),
|
||||
[deferredStore.source],
|
||||
[deferredStore.source]
|
||||
);
|
||||
|
||||
useMountEffect(() => {
|
||||
@@ -313,35 +299,34 @@ export default function Editor() {
|
||||
try {
|
||||
mountStore = initStoreFromUrlOrLocalStorage();
|
||||
} catch (e) {
|
||||
invariant(e instanceof Error, 'Only Error may be caught.');
|
||||
invariant(e instanceof Error, "Only Error may be caught.");
|
||||
enqueueSnackbar(e.message, {
|
||||
variant: 'message',
|
||||
variant: "message",
|
||||
...createMessage(
|
||||
'Bad URL - fell back to the default Playground.',
|
||||
"Bad URL - fell back to the default Playground.",
|
||||
MessageLevel.Info,
|
||||
MessageSource.Playground,
|
||||
MessageSource.Playground
|
||||
),
|
||||
});
|
||||
mountStore = defaultStore;
|
||||
}
|
||||
dispatchStore({
|
||||
type: 'setStore',
|
||||
payload: {store: mountStore},
|
||||
type: "setStore",
|
||||
payload: { store: mountStore },
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex basis top-14">
|
||||
<div className={clsx('relative sm:basis-1/4')}>
|
||||
<div className={clsx("relative sm:basis-1/4")}>
|
||||
<Input
|
||||
language={language}
|
||||
errors={
|
||||
compilerOutput.kind === 'err' ? compilerOutput.error.details : []
|
||||
compilerOutput.kind === "err" ? compilerOutput.error.details : []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={clsx('flex sm:flex flex-wrap')}>
|
||||
<div className={clsx("flex sm:flex flex-wrap")}>
|
||||
<Output store={deferredStore} compilerOutput={compilerOutput} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,28 +5,27 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
|
||||
import {CompilerErrorDetail} from 'babel-plugin-react-compiler/src';
|
||||
import invariant from 'invariant';
|
||||
import type {editor} from 'monaco-editor';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import {Resizable} from 're-resizable';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {renderReactCompilerMarkers} from '../../lib/reactCompilerMonacoDiagnostics';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
import MonacoEditor, { loader, type Monaco } from "@monaco-editor/react";
|
||||
import { CompilerErrorDetail } from "babel-plugin-react-compiler/src";
|
||||
import invariant from "invariant";
|
||||
import type { editor } from "monaco-editor";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { Resizable } from "re-resizable";
|
||||
import { useEffect, useState } from "react";
|
||||
import { renderReactCompilerMarkers } from "../../lib/reactCompilerMonacoDiagnostics";
|
||||
import { useStore, useStoreDispatch } from "../StoreContext";
|
||||
import { monacoOptions } from "./monacoOptions";
|
||||
// TODO: Make TS recognize .d.ts files, in addition to loading them with webpack.
|
||||
// @ts-ignore
|
||||
import React$Types from '../../node_modules/@types/react/index.d.ts';
|
||||
import React$Types from "../../node_modules/@types/react/index.d.ts";
|
||||
|
||||
loader.config({monaco});
|
||||
loader.config({ monaco });
|
||||
|
||||
type Props = {
|
||||
errors: CompilerErrorDetail[];
|
||||
language: 'flow' | 'typescript';
|
||||
};
|
||||
|
||||
export default function Input({errors, language}: Props) {
|
||||
export default function Input({ errors }: Props) {
|
||||
const [monaco, setMonaco] = useState<Monaco | null>(null);
|
||||
const store = useStore();
|
||||
const dispatchStore = useStoreDispatch();
|
||||
@@ -36,47 +35,18 @@ export default function Input({errors, language}: Props) {
|
||||
if (!monaco) return;
|
||||
const uri = monaco.Uri.parse(`file:///index.js`);
|
||||
const model = monaco.editor.getModel(uri);
|
||||
invariant(model, 'Model must exist for the selected input file.');
|
||||
renderReactCompilerMarkers({monaco, model, details: errors});
|
||||
invariant(model, "Model must exist for the selected input file.");
|
||||
renderReactCompilerMarkers({ monaco, model, details: errors });
|
||||
// N.B. that `tabSize` is a model property, not an editor property.
|
||||
// So, the tab size has to be set per model.
|
||||
model.updateOptions({tabSize: 2});
|
||||
model.updateOptions({ tabSize: 2 });
|
||||
}, [monaco, errors]);
|
||||
|
||||
const flowDiagnosticDisable = [
|
||||
7028 /* unused label */, 6133 /* var declared but not read */,
|
||||
];
|
||||
useEffect(() => {
|
||||
// Ignore "can only be used in TypeScript files." errors, since
|
||||
// we want to support syntax highlighting for Flow (*.js) files
|
||||
// and Flow is not a built-in language.
|
||||
if (!monaco) return;
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
diagnosticCodesToIgnore: [
|
||||
8002,
|
||||
8003,
|
||||
8004,
|
||||
8005,
|
||||
8006,
|
||||
8008,
|
||||
8009,
|
||||
8010,
|
||||
8011,
|
||||
8012,
|
||||
8013,
|
||||
...(language === 'flow' ? flowDiagnosticDisable : []),
|
||||
],
|
||||
noSemanticValidation: true,
|
||||
// Monaco can't validate Flow component syntax
|
||||
noSyntaxValidation: language === 'flow',
|
||||
});
|
||||
}, [monaco, language]);
|
||||
|
||||
const handleChange = (value: string | undefined) => {
|
||||
if (!value) return;
|
||||
|
||||
dispatchStore({
|
||||
type: 'updateFile',
|
||||
type: "updateFile",
|
||||
payload: {
|
||||
source: value,
|
||||
},
|
||||
@@ -86,16 +56,27 @@ export default function Input({errors, language}: Props) {
|
||||
const handleMount = (_: editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
setMonaco(monaco);
|
||||
|
||||
// Ignore "can only be used in TypeScript files." errors, since
|
||||
// we want to support syntax highlighting for Flow (*.js) files
|
||||
// and Flow is not a built-in language.
|
||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||
diagnosticCodesToIgnore: [
|
||||
8002, 8003, 8004, 8005, 8006, 8008, 8009, 8010, 8011, 8012, 8013,
|
||||
],
|
||||
noSemanticValidation: true,
|
||||
noSyntaxValidation: false,
|
||||
});
|
||||
|
||||
const tscOptions = {
|
||||
allowNonTsExtensions: true,
|
||||
target: monaco.languages.typescript.ScriptTarget.ES2015,
|
||||
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||
jsx: monaco.languages.typescript.JsxEmit.Preserve,
|
||||
typeRoots: ['node_modules/@types'],
|
||||
typeRoots: ["node_modules/@types"],
|
||||
allowSyntheticDefaultImports: true,
|
||||
};
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(
|
||||
tscOptions,
|
||||
tscOptions
|
||||
);
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
...tscOptions,
|
||||
@@ -106,7 +87,7 @@ export default function Input({errors, language}: Props) {
|
||||
// Add React type declarations to Monaco
|
||||
const reactLib = [
|
||||
React$Types,
|
||||
'file:///node_modules/@types/react/index.d.ts',
|
||||
"file:///node_modules/@types/react/index.d.ts",
|
||||
] as [any, string];
|
||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(...reactLib);
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(...reactLib);
|
||||
@@ -124,16 +105,17 @@ export default function Input({errors, language}: Props) {
|
||||
<div className="relative flex flex-col flex-none border-r border-gray-200">
|
||||
<Resizable
|
||||
minWidth={650}
|
||||
enable={{right: true}}
|
||||
enable={{ right: true }}
|
||||
// Restrict MonacoEditor's height, since the config autoLayout:true
|
||||
// will grow the editor to fit within parent element
|
||||
className="!h-[calc(100vh_-_3.5rem)]">
|
||||
className="!h-[calc(100vh_-_3.5rem)]"
|
||||
>
|
||||
<MonacoEditor
|
||||
path={'index.js'}
|
||||
path={"index.js"}
|
||||
// .js and .jsx files are specified to be TS so that Monaco can actually
|
||||
// check their syntax using its TS language service. They are still JS files
|
||||
// due to their extensions, so TS language features don't work.
|
||||
language={'javascript'}
|
||||
language={"javascript"}
|
||||
value={store.source}
|
||||
onMount={handleMount}
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -5,46 +5,46 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import generate from '@babel/generator';
|
||||
import * as t from '@babel/types';
|
||||
import generate from "@babel/generator";
|
||||
import * as t from "@babel/types";
|
||||
import {
|
||||
CodeIcon,
|
||||
DocumentAddIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
import MonacoEditor, {DiffEditor} from '@monaco-editor/react';
|
||||
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';
|
||||
import {memo, useEffect, useState} from 'react';
|
||||
import {type Store} from '../../lib/stores';
|
||||
import TabbedWindow from '../TabbedWindow';
|
||||
import {monacoOptions} from './monacoOptions';
|
||||
} from "@heroicons/react/outline";
|
||||
import MonacoEditor, { DiffEditor } from "@monaco-editor/react";
|
||||
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";
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { type Store } from "../../lib/stores";
|
||||
import TabbedWindow from "../TabbedWindow";
|
||||
import { monacoOptions } from "./monacoOptions";
|
||||
const MemoizedOutput = memo(Output);
|
||||
|
||||
export default MemoizedOutput;
|
||||
|
||||
export type PrintedCompilerPipelineValue =
|
||||
| {
|
||||
kind: 'ast';
|
||||
kind: "ast";
|
||||
name: string;
|
||||
fnName: string | null;
|
||||
value: t.FunctionDeclaration;
|
||||
}
|
||||
| {
|
||||
kind: 'hir';
|
||||
kind: "hir";
|
||||
name: string;
|
||||
fnName: string | null;
|
||||
value: string;
|
||||
}
|
||||
| {kind: 'reactive'; name: string; fnName: string | null; value: string}
|
||||
| {kind: 'debug'; name: string; fnName: string | null; value: string};
|
||||
| { kind: "reactive"; name: string; fnName: string | null; value: string }
|
||||
| { kind: "debug"; name: string; fnName: string | null; value: string };
|
||||
|
||||
export type CompilerOutput =
|
||||
| {kind: 'ok'; results: Map<string, PrintedCompilerPipelineValue[]>}
|
||||
| { kind: "ok"; results: Map<string, PrintedCompilerPipelineValue[]> }
|
||||
| {
|
||||
kind: 'err';
|
||||
kind: "err";
|
||||
results: Map<string, PrintedCompilerPipelineValue[]>;
|
||||
error: CompilerError;
|
||||
};
|
||||
@@ -63,7 +63,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
for (const result of results) {
|
||||
switch (result.kind) {
|
||||
case 'hir': {
|
||||
case "hir": {
|
||||
const prev = concattedResults.get(result.name);
|
||||
const next = result.value;
|
||||
const identName = `function ${result.fnName}`;
|
||||
@@ -74,7 +74,7 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
case "reactive": {
|
||||
const prev = concattedResults.get(passName);
|
||||
const next = result.value;
|
||||
if (prev != null) {
|
||||
@@ -84,29 +84,29 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ast':
|
||||
case "ast":
|
||||
topLevelFnDecls.push(result.value);
|
||||
break;
|
||||
case 'debug': {
|
||||
case "debug": {
|
||||
concattedResults.set(passName, result.value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const _: never = result;
|
||||
throw new Error('Unexpected result kind');
|
||||
throw new Error("Unexpected result kind");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let lastPassOutput: string | null = null;
|
||||
let nonDiffPasses = ['HIR', 'BuildReactiveFunction', 'EnvironmentConfig'];
|
||||
for (const [passName, text] of concattedResults) {
|
||||
tabs.set(
|
||||
passName,
|
||||
<TextTabContent
|
||||
output={text}
|
||||
diff={lastPassOutput}
|
||||
showInfoPanel={!nonDiffPasses.includes(passName)}></TextTabContent>,
|
||||
diff={lastPassOutput ?? null}
|
||||
showInfoPanel={true}
|
||||
></TextTabContent>
|
||||
);
|
||||
lastPassOutput = text;
|
||||
}
|
||||
@@ -115,24 +115,25 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
|
||||
// Make a synthetic Program so we can have a single AST with all the top level
|
||||
// FunctionDeclarations
|
||||
const ast = t.program(topLevelFnDecls);
|
||||
const {code, sourceMapUrl} = await codegen(ast, source);
|
||||
const { code, sourceMapUrl } = await codegen(ast, source);
|
||||
reorderedTabs.set(
|
||||
'JS',
|
||||
"JS",
|
||||
<TextTabContent
|
||||
output={code}
|
||||
diff={null}
|
||||
showInfoPanel={false}></TextTabContent>,
|
||||
showInfoPanel={false}
|
||||
></TextTabContent>
|
||||
);
|
||||
if (sourceMapUrl) {
|
||||
reorderedTabs.set(
|
||||
'SourceMap',
|
||||
"SourceMap",
|
||||
<>
|
||||
<iframe
|
||||
src={sourceMapUrl}
|
||||
className="w-full h-monaco_small sm:h-monaco"
|
||||
title="Generated Code"
|
||||
/>
|
||||
</>,
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -144,23 +145,23 @@ async function tabify(source: string, compilerOutput: CompilerOutput) {
|
||||
|
||||
async function codegen(
|
||||
ast: t.Program,
|
||||
source: string,
|
||||
): Promise<{code: any; sourceMapUrl: string | null}> {
|
||||
source: string
|
||||
): Promise<{ code: any; sourceMapUrl: string | null }> {
|
||||
const generated = generate(
|
||||
ast,
|
||||
{sourceMaps: true, sourceFileName: 'input.js'},
|
||||
source,
|
||||
{ sourceMaps: true, sourceFileName: "input.js" },
|
||||
source
|
||||
);
|
||||
const sourceMapUrl = getSourceMapUrl(
|
||||
generated.code,
|
||||
JSON.stringify(generated.map),
|
||||
JSON.stringify(generated.map)
|
||||
);
|
||||
const codegenOutput = await prettier.format(generated.code, {
|
||||
semi: true,
|
||||
parser: 'babel',
|
||||
parser: "babel",
|
||||
plugins: [parserBabel, prettierPluginEstree],
|
||||
});
|
||||
return {code: codegenOutput, sourceMapUrl};
|
||||
return { code: codegenOutput, sourceMapUrl };
|
||||
}
|
||||
|
||||
function utf16ToUTF8(s: string): string {
|
||||
@@ -171,27 +172,27 @@ function getSourceMapUrl(code: string, map: string): string | null {
|
||||
code = utf16ToUTF8(code);
|
||||
map = utf16ToUTF8(map);
|
||||
return `https://evanw.github.io/source-map-visualization/#${btoa(
|
||||
`${code.length}\0${code}${map.length}\0${map}`,
|
||||
`${code.length}\0${code}${map.length}\0${map}`
|
||||
)}`;
|
||||
}
|
||||
|
||||
function Output({store, compilerOutput}: Props) {
|
||||
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(['JS']));
|
||||
function Output({ store, compilerOutput }: Props) {
|
||||
const [tabsOpen, setTabsOpen] = useState<Set<string>>(() => new Set(["JS"]));
|
||||
const [tabs, setTabs] = useState<Map<string, React.ReactNode>>(
|
||||
() => new Map(),
|
||||
() => new Map()
|
||||
);
|
||||
useEffect(() => {
|
||||
tabify(store.source, compilerOutput).then(tabs => {
|
||||
tabify(store.source, compilerOutput).then((tabs) => {
|
||||
setTabs(tabs);
|
||||
});
|
||||
}, [store.source, compilerOutput]);
|
||||
|
||||
const changedPasses: Set<string> = new Set(['JS', 'HIR']); // Initial and final passes should always be bold
|
||||
let lastResult: string = '';
|
||||
const changedPasses: Set<string> = new Set();
|
||||
let lastResult: string = "";
|
||||
for (const [passName, results] of compilerOutput.results) {
|
||||
for (const result of results) {
|
||||
let currResult = '';
|
||||
if (result.kind === 'hir' || result.kind === 'reactive') {
|
||||
let currResult = "";
|
||||
if (result.kind === "hir" || result.kind === "reactive") {
|
||||
currResult += `function ${result.fnName}\n\n${result.value}`;
|
||||
}
|
||||
if (currResult !== lastResult) {
|
||||
@@ -210,16 +211,18 @@ function Output({store, compilerOutput}: Props) {
|
||||
tabs={tabs}
|
||||
changedPasses={changedPasses}
|
||||
/>
|
||||
{compilerOutput.kind === 'err' ? (
|
||||
{compilerOutput.kind === "err" ? (
|
||||
<div
|
||||
className="flex flex-wrap absolute bottom-0 bg-white grow border-y border-grey-200 transition-all ease-in"
|
||||
style={{width: 'calc(100vw - 650px)'}}>
|
||||
style={{ width: "calc(100vw - 650px)" }}
|
||||
>
|
||||
<div className="w-full p-4 basis-full border-b">
|
||||
<h2>COMPILER ERRORS</h2>
|
||||
</div>
|
||||
<pre
|
||||
className="p-4 basis-full text-red-600 overflow-y-scroll whitespace-pre-wrap"
|
||||
style={{width: 'calc(100vw - 650px)', height: '150px'}}>
|
||||
style={{ width: "calc(100vw - 650px)", height: "150px" }}
|
||||
>
|
||||
<code>{compilerOutput.error.toString()}</code>
|
||||
</pre>
|
||||
</div>
|
||||
@@ -247,7 +250,8 @@ function TextTabContent({
|
||||
{diff != null && output !== diff ? (
|
||||
<button
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
onClick={() => setDiffMode(diffMode => !diffMode)}>
|
||||
onClick={() => setDiffMode((diffMode) => !diffMode)}
|
||||
>
|
||||
{!diffMode ? (
|
||||
<>
|
||||
<DocumentAddIcon className="w-5 h-5" /> Show Diff
|
||||
@@ -275,7 +279,7 @@ function TextTabContent({
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
lineNumbers: "off",
|
||||
glyphMargin: false,
|
||||
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
|
||||
lineDecorationsWidth: 0,
|
||||
@@ -289,7 +293,7 @@ function TextTabContent({
|
||||
options={{
|
||||
...monacoOptions,
|
||||
readOnly: true,
|
||||
lineNumbers: 'off',
|
||||
lineNumbers: "off",
|
||||
glyphMargin: false,
|
||||
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
|
||||
lineDecorationsWidth: 0,
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
// monaco-editor is currently not compatible with ssr
|
||||
// https://github.com/vercel/next.js/issues/31692
|
||||
const Editor = dynamic(() => import('./EditorImpl'), {
|
||||
const Editor = dynamic(() => import("./EditorImpl"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {EditorProps} from '@monaco-editor/react';
|
||||
import type { EditorProps } from "@monaco-editor/react";
|
||||
|
||||
export const monacoOptions: Partial<EditorProps['options']> = {
|
||||
export const monacoOptions: Partial<EditorProps["options"]> = {
|
||||
fontSize: 14,
|
||||
padding: {top: 8},
|
||||
padding: { top: 8 },
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 10,
|
||||
alwaysConsumeMouseWheel: false,
|
||||
@@ -22,11 +22,11 @@ export const monacoOptions: Partial<EditorProps['options']> = {
|
||||
fontFamily: '"Source Code Pro", monospace',
|
||||
glyphMargin: true,
|
||||
|
||||
autoClosingBrackets: 'languageDefined',
|
||||
autoClosingDelete: 'always',
|
||||
autoClosingOvertype: 'always',
|
||||
autoClosingBrackets: "languageDefined",
|
||||
autoClosingDelete: "always",
|
||||
autoClosingOvertype: "always",
|
||||
|
||||
automaticLayout: true,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'deepIndent',
|
||||
wordWrap: "on",
|
||||
wrappingIndent: "deepIndent",
|
||||
};
|
||||
|
||||
@@ -5,24 +5,24 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {RefreshIcon, ShareIcon} from '@heroicons/react/outline';
|
||||
import {CheckIcon} from '@heroicons/react/solid';
|
||||
import clsx from 'clsx';
|
||||
import Link from 'next/link';
|
||||
import {useSnackbar} from 'notistack';
|
||||
import {useState} from 'react';
|
||||
import {defaultStore} from '../lib/defaultStore';
|
||||
import {IconGitHub} from './Icons/IconGitHub';
|
||||
import Logo from './Logo';
|
||||
import {useStoreDispatch} from './StoreContext';
|
||||
import { RefreshIcon, ShareIcon } from "@heroicons/react/outline";
|
||||
import { CheckIcon } from "@heroicons/react/solid";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useState } from "react";
|
||||
import { defaultStore } from "../lib/defaultStore";
|
||||
import { IconGitHub } from "./Icons/IconGitHub";
|
||||
import Logo from "./Logo";
|
||||
import { useStoreDispatch } from "./StoreContext";
|
||||
|
||||
export default function Header() {
|
||||
const [showCheck, setShowCheck] = useState(false);
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const {enqueueSnackbar, closeSnackbar} = useSnackbar();
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
const handleReset = () => {
|
||||
if (confirm('Are you sure you want to reset the playground?')) {
|
||||
if (confirm("Are you sure you want to reset the playground?")) {
|
||||
/*
|
||||
Close open snackbars if any. This is necessary because when displaying
|
||||
outputs (Preview or not), we only close previous snackbars if we received
|
||||
@@ -31,13 +31,13 @@ export default function Header() {
|
||||
such as "Bad URL" will be closed by the outputs calling `closeSnackbar`.
|
||||
*/
|
||||
closeSnackbar();
|
||||
dispatchStore({type: 'setStore', payload: {store: defaultStore}});
|
||||
dispatchStore({ type: "setStore", payload: { store: defaultStore } });
|
||||
}
|
||||
};
|
||||
|
||||
const handleShare = () => {
|
||||
navigator.clipboard.writeText(location.href).then(() => {
|
||||
enqueueSnackbar('URL copied to clipboard');
|
||||
enqueueSnackbar("URL copied to clipboard");
|
||||
setShowCheck(true);
|
||||
// Show the check mark icon briefly after URL is copied
|
||||
setTimeout(() => setShowCheck(false), 1000);
|
||||
@@ -49,8 +49,8 @@ export default function Header() {
|
||||
<div className="flex items-center flex-none h-full gap-2 text-lg">
|
||||
<Logo
|
||||
className={clsx(
|
||||
'w-8 h-8 text-link',
|
||||
process.env.NODE_ENV === 'development' && 'text-yellow-600',
|
||||
"w-8 h-8 text-link",
|
||||
process.env.NODE_ENV === "development" && "text-yellow-600"
|
||||
)}
|
||||
/>
|
||||
<p className="hidden select-none sm:block">React Compiler Playground</p>
|
||||
@@ -60,7 +60,8 @@ export default function Header() {
|
||||
title="Reset Playground"
|
||||
aria-label="Reset Playground"
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
onClick={handleReset}>
|
||||
onClick={handleReset}
|
||||
>
|
||||
<RefreshIcon className="w-5 h-5" />
|
||||
<p className="hidden sm:block">Reset</p>
|
||||
</button>
|
||||
@@ -69,7 +70,8 @@ export default function Header() {
|
||||
aria-label="Copy sharable URL"
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
onClick={handleShare}
|
||||
disabled={showCheck}>
|
||||
disabled={showCheck}
|
||||
>
|
||||
{!showCheck ? (
|
||||
<ShareIcon className="w-5 h-5" />
|
||||
) : (
|
||||
@@ -82,7 +84,8 @@ export default function Header() {
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
aria-label="Open on GitHub"
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link">
|
||||
className="flex items-center gap-1 transition-colors duration-150 ease-in text-secondary hover:text-link"
|
||||
>
|
||||
<IconGitHub />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {memo} from 'react';
|
||||
import { memo } from "react";
|
||||
|
||||
export const IconGitHub = memo<JSX.IntrinsicElements['svg']>(
|
||||
export const IconGitHub = memo<JSX.IntrinsicElements["svg"]>(
|
||||
function IconGitHub(props) {
|
||||
return (
|
||||
<svg
|
||||
@@ -16,9 +16,10 @@ export const IconGitHub = memo<JSX.IntrinsicElements['svg']>(
|
||||
height="1.5em"
|
||||
viewBox="0 -2 24 24"
|
||||
fill="currentColor"
|
||||
{...props}>
|
||||
{...props}
|
||||
>
|
||||
<path d="M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0"></path>
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
|
||||
// https://github.com/reactjs/reactjs.org/blob/main/beta/src/components/Logo.tsx
|
||||
|
||||
export default function Logo(props: JSX.IntrinsicElements['svg']) {
|
||||
export default function Logo(props: JSX.IntrinsicElements["svg"]) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 410 369"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M204.995 224.552C226.56 224.552 244.042 207.07 244.042 185.506C244.042 163.941 226.56 146.459 204.995 146.459C183.43 146.459 165.948 163.941 165.948 185.506C165.948 207.07 183.43 224.552 204.995 224.552Z"
|
||||
fill="currentColor"
|
||||
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
ExclamationIcon,
|
||||
InformationCircleIcon,
|
||||
XIcon,
|
||||
} from '@heroicons/react/solid';
|
||||
import {CustomContentProps, SnackbarContent, useSnackbar} from 'notistack';
|
||||
import {forwardRef} from 'react';
|
||||
import {MessageLevel, MessageSource} from '../lib/stores';
|
||||
} from "@heroicons/react/solid";
|
||||
import { CustomContentProps, SnackbarContent, useSnackbar } from "notistack";
|
||||
import { forwardRef } from "react";
|
||||
import { MessageLevel, MessageSource } from "../lib/stores";
|
||||
|
||||
// https://notistack.com/examples/advanced/custom-component#custom-variant-(typescript)
|
||||
declare module 'notistack' {
|
||||
declare module "notistack" {
|
||||
interface VariantOverrides {
|
||||
message: {
|
||||
title: string;
|
||||
@@ -34,14 +34,15 @@ interface MessageProps extends CustomContentProps {
|
||||
}
|
||||
|
||||
const Message = forwardRef<HTMLDivElement, MessageProps>(
|
||||
({id, title, level, source, codeframe}, ref) => {
|
||||
const {closeSnackbar} = useSnackbar();
|
||||
({ id, title, level, source, codeframe }, ref) => {
|
||||
const { closeSnackbar } = useSnackbar();
|
||||
const isDismissible = source !== MessageSource.Playground;
|
||||
|
||||
return (
|
||||
<SnackbarContent
|
||||
ref={ref}
|
||||
className="flex items-start justify-between gap-3 px-4 py-3 text-sm bg-white border rounded-md shadow w-toast">
|
||||
className="flex items-start justify-between gap-3 px-4 py-3 text-sm bg-white border rounded-md shadow w-toast"
|
||||
>
|
||||
<div className="flex gap-3 w-toast-body">
|
||||
{level === MessageLevel.Warning ? (
|
||||
<div className="flex items-center justify-center flex-none rounded-md w-7 h-7 bg-amber-100">
|
||||
@@ -68,15 +69,16 @@ const Message = forwardRef<HTMLDivElement, MessageProps>(
|
||||
{isDismissible ? (
|
||||
<button
|
||||
className="flex items-center justify-center flex-none transition-colors duration-150 ease-in rounded-md justify-self-end group w-7 h-7 hover:bg-gray-200"
|
||||
onClick={() => closeSnackbar(id)}>
|
||||
onClick={() => closeSnackbar(id)}
|
||||
>
|
||||
<XIcon className="w-5 h-5 fill-gray-500 group-hover:fill-gray-800" />
|
||||
</button>
|
||||
) : null}
|
||||
</SnackbarContent>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Message.displayName = 'MessageComponent';
|
||||
Message.displayName = "MessageComponent";
|
||||
|
||||
export default Message;
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {Dispatch, ReactNode} from 'react';
|
||||
import {useReducer} from 'react';
|
||||
import createContext from '../lib/createContext';
|
||||
import {emptyStore} from '../lib/defaultStore';
|
||||
import type {Store} from '../lib/stores';
|
||||
import {saveStore} from '../lib/stores';
|
||||
import type { Dispatch, ReactNode } from "react";
|
||||
import { useReducer } from "react";
|
||||
import createContext from "../lib/createContext";
|
||||
import { emptyStore } from "../lib/defaultStore";
|
||||
import type { Store } from "../lib/stores";
|
||||
import { saveStore } from "../lib/stores";
|
||||
|
||||
const StoreContext = createContext<Store>();
|
||||
|
||||
@@ -29,7 +29,7 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
|
||||
/**
|
||||
* Make Store and dispatch function available to all sub-components in children.
|
||||
*/
|
||||
export function StoreProvider({children}: {children: ReactNode}) {
|
||||
export function StoreProvider({ children }: { children: ReactNode }) {
|
||||
const [store, dispatch] = useReducer(storeReducer, emptyStore);
|
||||
|
||||
return (
|
||||
@@ -43,13 +43,13 @@ export function StoreProvider({children}: {children: ReactNode}) {
|
||||
|
||||
type ReducerAction =
|
||||
| {
|
||||
type: 'setStore';
|
||||
type: "setStore";
|
||||
payload: {
|
||||
store: Store;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: 'updateFile';
|
||||
type: "updateFile";
|
||||
payload: {
|
||||
source: string;
|
||||
};
|
||||
@@ -57,14 +57,14 @@ type ReducerAction =
|
||||
|
||||
function storeReducer(store: Store, action: ReducerAction): Store {
|
||||
switch (action.type) {
|
||||
case 'setStore': {
|
||||
case "setStore": {
|
||||
const newStore = action.payload.store;
|
||||
|
||||
saveStore(newStore);
|
||||
return newStore;
|
||||
}
|
||||
case 'updateFile': {
|
||||
const {source} = action.payload;
|
||||
case "updateFile": {
|
||||
const { source } = action.payload;
|
||||
|
||||
const newStore = {
|
||||
...store,
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Resizable} from 're-resizable';
|
||||
import React, {useCallback} from 'react';
|
||||
import { Resizable } from "re-resizable";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
type TabsRecord = Map<string, React.ReactNode>;
|
||||
|
||||
@@ -21,14 +21,15 @@ export default function TabbedWindow(props: {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-center"
|
||||
style={{width: 'calc(100vw - 650px)'}}>
|
||||
style={{ width: "calc(100vw - 650px)" }}
|
||||
>
|
||||
No compiler output detected, see errors below
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-row">
|
||||
{Array.from(props.tabs.keys()).map(name => {
|
||||
{Array.from(props.tabs.keys()).map((name) => {
|
||||
return (
|
||||
<TabbedWindowItem
|
||||
name={name}
|
||||
@@ -72,14 +73,13 @@ function TabbedWindowItem({
|
||||
return (
|
||||
<div key={name} className="flex flex-row">
|
||||
{isShow ? (
|
||||
<Resizable className="border-r" minWidth={550} enable={{right: true}}>
|
||||
<Resizable className="border-r" minWidth={550} enable={{ right: true }}>
|
||||
<h2
|
||||
title="Minimize tab"
|
||||
aria-label="Minimize tab"
|
||||
onClick={toggleTabs}
|
||||
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${
|
||||
hasChanged ? 'font-bold' : 'font-light'
|
||||
} text-secondary hover:text-link`}>
|
||||
className={`p-4 duration-150 ease-in border-b cursor-pointer border-grey-200 ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
|
||||
>
|
||||
- {name}
|
||||
</h2>
|
||||
{tabs.get(name) ?? <div>No output for {name}</div>}
|
||||
@@ -89,11 +89,10 @@ function TabbedWindowItem({
|
||||
<button
|
||||
title={`Expand compiler tab: ${name}`}
|
||||
aria-label={`Expand compiler tab: ${name}`}
|
||||
style={{transform: 'rotate(90deg) translate(-50%)'}}
|
||||
style={{ transform: "rotate(90deg) translate(-50%)" }}
|
||||
onClick={toggleTabs}
|
||||
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${
|
||||
hasChanged ? 'font-bold' : 'font-light'
|
||||
} text-secondary hover:text-link`}>
|
||||
className={`flex-grow-0 w-5 transition-colors duration-150 ease-in ${hasChanged ? "font-bold" : "font-light"} text-secondary hover:text-link`}
|
||||
>
|
||||
{name}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export {default as Editor} from './Editor';
|
||||
export {default as Header} from './Header';
|
||||
export {StoreProvider} from './StoreContext';
|
||||
export { default as Editor } from "./Editor";
|
||||
export { default as Header } from "./Header";
|
||||
export { StoreProvider } from "./StoreContext";
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export {default as useMountEffect} from './useMountEffect';
|
||||
export { default as useMountEffect } from "./useMountEffect";
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {EffectCallback} from 'react';
|
||||
import {useEffect} from 'react';
|
||||
import type { EffectCallback } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function useMountEffect(effect: EffectCallback) {
|
||||
return useEffect(effect, []);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* Replacement to React.createContext.
|
||||
@@ -29,9 +29,9 @@ export default function createContext<T>() {
|
||||
function useContext() {
|
||||
const c = React.useContext(context);
|
||||
if (!c)
|
||||
throw new Error('useContext must be within a Provider with a value');
|
||||
throw new Error("useContext must be within a Provider with a value");
|
||||
return c;
|
||||
}
|
||||
|
||||
return {useContext, Provider: context.Provider};
|
||||
return { useContext, Provider: context.Provider };
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {Store} from './stores';
|
||||
import type { Store } from "./stores";
|
||||
|
||||
const index = `\
|
||||
export default function MyApp() {
|
||||
@@ -18,5 +18,5 @@ export const defaultStore: Store = {
|
||||
};
|
||||
|
||||
export const emptyStore: Store = {
|
||||
source: '',
|
||||
source: "",
|
||||
};
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Monaco} from '@monaco-editor/react';
|
||||
import { Monaco } from "@monaco-editor/react";
|
||||
import {
|
||||
CompilerErrorDetail,
|
||||
ErrorSeverity,
|
||||
} from 'babel-plugin-react-compiler/src';
|
||||
import {MarkerSeverity, type editor} from 'monaco-editor';
|
||||
} from "babel-plugin-react-compiler/src";
|
||||
import { MarkerSeverity, type editor } from "monaco-editor";
|
||||
|
||||
function mapReactCompilerSeverityToMonaco(
|
||||
level: ErrorSeverity,
|
||||
monaco: Monaco,
|
||||
monaco: Monaco
|
||||
): MarkerSeverity {
|
||||
switch (level) {
|
||||
case ErrorSeverity.Todo:
|
||||
@@ -26,9 +26,9 @@ function mapReactCompilerSeverityToMonaco(
|
||||
|
||||
function mapReactCompilerDiagnosticToMonacoMarker(
|
||||
detail: CompilerErrorDetail,
|
||||
monaco: Monaco,
|
||||
monaco: Monaco
|
||||
): editor.IMarkerData | null {
|
||||
if (detail.loc == null || typeof detail.loc === 'symbol') {
|
||||
if (detail.loc == null || typeof detail.loc === "symbol") {
|
||||
return null;
|
||||
}
|
||||
const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco);
|
||||
@@ -63,27 +63,27 @@ export function renderReactCompilerMarkers({
|
||||
markers.push(marker);
|
||||
}
|
||||
if (markers.length > 0) {
|
||||
monaco.editor.setModelMarkers(model, 'owner', markers);
|
||||
const newDecorations = markers.map(marker => {
|
||||
monaco.editor.setModelMarkers(model, "owner", markers);
|
||||
const newDecorations = markers.map((marker) => {
|
||||
return {
|
||||
range: new monaco.Range(
|
||||
marker.startLineNumber,
|
||||
marker.startColumn,
|
||||
marker.endLineNumber,
|
||||
marker.endColumn,
|
||||
marker.endColumn
|
||||
),
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
glyphMarginClassName: 'bg-red-300',
|
||||
glyphMarginClassName: "bg-red-300",
|
||||
},
|
||||
};
|
||||
});
|
||||
decorations = model.deltaDecorations(decorations, newDecorations);
|
||||
} else {
|
||||
monaco.editor.setModelMarkers(model, 'owner', []);
|
||||
monaco.editor.setModelMarkers(model, "owner", []);
|
||||
decorations = model.deltaDecorations(
|
||||
model.getAllDecorations().map(d => d.id),
|
||||
[],
|
||||
model.getAllDecorations().map((d) => d.id),
|
||||
[]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './messages';
|
||||
export * from './store';
|
||||
export * from "./messages";
|
||||
export * from "./store";
|
||||
|
||||
@@ -27,10 +27,10 @@ export interface Message {
|
||||
export function createMessage(
|
||||
message: string,
|
||||
level: MessageLevel,
|
||||
source: MessageSource,
|
||||
source: MessageSource
|
||||
): Message {
|
||||
const [title, ...body] = message.split('\n');
|
||||
const codeframe = body.length > 0 ? body.join('\n') : undefined;
|
||||
const [title, ...body] = message.split("\n");
|
||||
const codeframe = body.length > 0 ? body.join("\n") : undefined;
|
||||
|
||||
return {
|
||||
source,
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import invariant from 'invariant';
|
||||
import invariant from "invariant";
|
||||
import {
|
||||
compressToEncodedURIComponent,
|
||||
decompressFromEncodedURIComponent,
|
||||
} from 'lz-string';
|
||||
import {defaultStore} from '../defaultStore';
|
||||
} from "lz-string";
|
||||
import { defaultStore } from "../defaultStore";
|
||||
|
||||
/**
|
||||
* Global Store for Playground
|
||||
@@ -30,8 +30,8 @@ export function decodeStore(hash: string): Store {
|
||||
*/
|
||||
export function saveStore(store: Store) {
|
||||
const hash = encodeStore(store);
|
||||
localStorage.setItem('playgroundStore', hash);
|
||||
history.replaceState({}, '', `#${hash}`);
|
||||
localStorage.setItem("playgroundStore", hash);
|
||||
history.replaceState({}, "", `#${hash}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,9 +41,9 @@ export function saveStore(store: Store) {
|
||||
function isValidStore(raw: unknown): raw is Store {
|
||||
return (
|
||||
raw != null &&
|
||||
typeof raw == 'object' &&
|
||||
'source' in raw &&
|
||||
typeof raw['source'] === 'string'
|
||||
typeof raw == "object" &&
|
||||
"source" in raw &&
|
||||
typeof raw["source"] === "string"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ function isValidStore(raw: unknown): raw is Store {
|
||||
*/
|
||||
export function initStoreFromUrlOrLocalStorage(): Store {
|
||||
const encodedSourceFromUrl = location.hash.slice(1);
|
||||
const encodedSourceFromLocal = localStorage.getItem('playgroundStore');
|
||||
const encodedSourceFromLocal = localStorage.getItem("playgroundStore");
|
||||
const encodedSource = encodedSourceFromUrl || encodedSourceFromLocal;
|
||||
|
||||
// No data in the URL and no data in the localStorage to fallback to.
|
||||
@@ -62,6 +62,6 @@ export function initStoreFromUrlOrLocalStorage(): Store {
|
||||
|
||||
const raw = decodeStore(encodedSource);
|
||||
|
||||
invariant(isValidStore(raw), 'Invalid Store');
|
||||
invariant(isValidStore(raw), "Invalid Store");
|
||||
return raw;
|
||||
}
|
||||
|
||||
20
compiler/apps/playground/lib/types.d.ts
vendored
20
compiler/apps/playground/lib/types.d.ts
vendored
@@ -1,20 +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.
|
||||
*/
|
||||
|
||||
// v0.17.1
|
||||
declare module "hermes-parser" {
|
||||
type HermesParserOptions = {
|
||||
allowReturnOutsideFunction?: boolean;
|
||||
babel?: boolean;
|
||||
flow?: "all" | "detect";
|
||||
enableExperimentalComponentSyntax?: boolean;
|
||||
sourceFilename?: string;
|
||||
sourceType?: "module" | "script" | "unambiguous";
|
||||
tokens?: boolean;
|
||||
};
|
||||
export function parse(code: string, options: Partial<HermesParserOptions>);
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
|
||||
const path = require('path');
|
||||
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
const path = require("path");
|
||||
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
@@ -14,36 +14,31 @@ const nextConfig = {
|
||||
// Load *.d.ts files as strings using https://webpack.js.org/guides/asset-modules/#source-assets.
|
||||
config.module.rules.push({
|
||||
test: /\.d\.ts/,
|
||||
type: 'asset/source',
|
||||
type: "asset/source",
|
||||
});
|
||||
|
||||
// Monaco Editor
|
||||
if (!options.isServer) {
|
||||
config.plugins.push(
|
||||
new MonacoWebpackPlugin({
|
||||
languages: ['typescript', 'javascript'],
|
||||
filename: 'static/[name].worker.js',
|
||||
languages: ["typescript", "javascript"],
|
||||
filename: "static/[name].worker.js",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
'react-compiler-runtime': path.resolve(
|
||||
"react-compiler-runtime": path.resolve(
|
||||
__dirname,
|
||||
'../../packages/react-compiler-runtime'
|
||||
"../../packages/react-compiler-runtime"
|
||||
),
|
||||
};
|
||||
config.resolve.fallback = {
|
||||
fs: false,
|
||||
path: false,
|
||||
os: false,
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
|
||||
transpilePackages: ['monaco-editor'],
|
||||
transpilePackages: ["monaco-editor"],
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
"vercel-build": "yarn workspaces run build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"postinstall": "yarn playwright install",
|
||||
"test": "playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.19.1",
|
||||
"@babel/generator": "^7.19.1",
|
||||
"@babel/parser": "^7.19.1",
|
||||
"@babel/plugin-syntax-typescript": "^7.18.6",
|
||||
"@babel/plugin-transform-block-scoping": "^7.18.9",
|
||||
@@ -25,36 +25,37 @@
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@use-gesture/react": "^10.2.22",
|
||||
"fs": "^0.0.1-security",
|
||||
"hermes-eslint": "^0.14.0",
|
||||
"hermes-parser": "^0.22.0",
|
||||
"invariant": "^2.2.4",
|
||||
"lz-string": "^1.5.0",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"next": "^13.5.6",
|
||||
"notistack": "^3.0.0-alpha.7",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier": "3.0.3",
|
||||
"pretty-format": "^29.3.1",
|
||||
"re-resizable": "^6.9.16",
|
||||
"react": "18.2.0",
|
||||
"react-compiler-runtime": "*",
|
||||
"react-dom": "18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"react-compiler-runtime": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.9",
|
||||
"@types/prettier": "^2.7.1",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"clsx": "^1.2.1",
|
||||
"eslint": "^8.28.0",
|
||||
"eslint-config-next": "^13.5.6",
|
||||
"hermes-parser": "^0.22.0",
|
||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"./**/@babel/parser": "7.7.4",
|
||||
"./**/@babel/types": "7.7.4"
|
||||
"./**/@babel/types": "7.7.4",
|
||||
"@babel/core": "7.2.0",
|
||||
"@babel/traverse": "7.1.6",
|
||||
"@babel/generator": "7.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {defineConfig, devices} from '@playwright/test';
|
||||
import path from 'path';
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import path from "path";
|
||||
|
||||
// Use process.env.PORT by default and fallback to port 3000
|
||||
const PORT = process.env.PORT || 3000;
|
||||
@@ -19,19 +19,19 @@ export default defineConfig({
|
||||
// Timeout per test
|
||||
timeout: 30 * 1000,
|
||||
// Test directory
|
||||
testDir: path.join(__dirname, '__tests__/e2e'),
|
||||
testDir: path.join(__dirname, "__tests__/e2e"),
|
||||
// If a test fails, retry it additional 2 times
|
||||
retries: 2,
|
||||
// Artifacts folder where screenshots, videos, and traces are stored.
|
||||
outputDir: 'test-results/',
|
||||
outputDir: "test-results/",
|
||||
// Note: we only use text snapshots, so its safe to omit the host environment name
|
||||
snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
|
||||
snapshotPathTemplate: "{testDir}/__snapshots__/{testFilePath}/{arg}{ext}",
|
||||
|
||||
// Run your local dev server before starting the tests:
|
||||
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
|
||||
webServer: {
|
||||
command:
|
||||
'yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev',
|
||||
"yarn workspace babel-plugin-react-compiler build && yarn workspace react-compiler-runtime build && yarn dev",
|
||||
url: baseURL,
|
||||
timeout: 300 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
@@ -44,7 +44,7 @@ export default defineConfig({
|
||||
|
||||
// Retry a test if its failing with enabled tracing. This allows you to analyze the DOM, console logs, network traffic etc.
|
||||
// More information: https://playwright.dev/docs/trace-viewer
|
||||
trace: 'retry-with-trace',
|
||||
trace: "retry-with-trace",
|
||||
|
||||
// All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context
|
||||
// contextOptions: {
|
||||
@@ -54,8 +54,8 @@ export default defineConfig({
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {...devices['Desktop Chrome']},
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
// {
|
||||
// name: 'Desktop Firefox',
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const {execSync} = require('child_process');
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
// So that we don't need to check them into the repo.
|
||||
// See https://github.com/reactjs/reactjs.org/blob/main/beta/scripts/downloadFonts.js.
|
||||
execSync(
|
||||
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2'
|
||||
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Lt.woff2 --output public/fonts/Optimistic_Display_W_Lt.woff2"
|
||||
);
|
||||
execSync(
|
||||
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2'
|
||||
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Md.woff2 --output public/fonts/Optimistic_Display_W_Md.woff2"
|
||||
);
|
||||
execSync(
|
||||
'curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2'
|
||||
"curl https://conf.reactjs.org/fonts/Optimistic_Display_W_Bd.woff2 --output public/fonts/Optimistic_Display_W_Bd.woff2"
|
||||
);
|
||||
|
||||
@@ -5,33 +5,33 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const defaultTheme = require('tailwindcss/defaultTheme');
|
||||
const colors = require('./colors');
|
||||
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||
const colors = require("./colors");
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./app/**/*.{js,ts,jsx,tsx}',
|
||||
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
'./lib/forgetMonacoDiagnostics.ts',
|
||||
"./app/**/*.{js,ts,jsx,tsx}",
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
"./lib/forgetMonacoDiagnostics.ts",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors,
|
||||
width: {
|
||||
toast: 'min(900px, 100vw - 40px)',
|
||||
'toast-body': 'calc(100% - 60px)',
|
||||
'toast-title': 'calc(100% - 40px)',
|
||||
toast: "min(900px, 100vw - 40px)",
|
||||
"toast-body": "calc(100% - 60px)",
|
||||
"toast-title": "calc(100% - 40px)",
|
||||
},
|
||||
height: {
|
||||
content: 'calc(100vh - 45px)',
|
||||
monaco: 'calc(100vh - 93px)',
|
||||
monaco_small: 'calc(100vh - 129px)',
|
||||
content: "calc(100vh - 45px)",
|
||||
monaco: "calc(100vh - 93px)",
|
||||
monaco_small: "calc(100vh - 129px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'Optimistic Display',
|
||||
'-apple-system',
|
||||
"Optimistic Display",
|
||||
"-apple-system",
|
||||
...defaultTheme.fontFamily.sans,
|
||||
],
|
||||
},
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react.git"
|
||||
"url": "git+https://github.com/facebook/react-forget.git"
|
||||
},
|
||||
"scripts": {
|
||||
"bundle:meta": "scripts/bundle-meta.sh",
|
||||
"bundle:oss": "scripts/bundle-oss.sh",
|
||||
"copyright": "node scripts/copyright.js",
|
||||
"hash": "scripts/hash.sh",
|
||||
"start": "yarn workspace playground run start",
|
||||
@@ -22,6 +24,8 @@
|
||||
"build": "yarn workspaces run build",
|
||||
"dev": "concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && yarn workspace playground run dev\"",
|
||||
"test": "yarn workspaces run test",
|
||||
"prettier:write": "prettier --write . --log-level=warn",
|
||||
"prettier:ci": "prettier --check . --log-level=warn",
|
||||
"snap": "yarn workspace babel-plugin-react-compiler run snap",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
"postinstall": "perl -p -i -e 's/react\\.element/react.transitional.element/' packages/snap/node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' packages/babel-plugin-react-compiler/node_modules/react-dom/cjs/react-dom-test-utils.development.js",
|
||||
@@ -38,8 +42,7 @@
|
||||
"concurrently": "^7.4.0",
|
||||
"folder-hash": "^4.0.4",
|
||||
"ora": "5.4.1",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-hermes-parser": "^0.23.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prompt-promise": "^1.0.3",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-banner2": "^1.2.3",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
/** @type {import('jest').Config} */
|
||||
const config = {
|
||||
projects: ['<rootDir>/scripts/jest/*.config.js'],
|
||||
projects: ["<rootDir>/scripts/jest/*.config.js"],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "babel-plugin-react-compiler",
|
||||
"version": "0.0.0-experimental-334f00b-20240725",
|
||||
"version": "0.0.0-experimental-938cd9a-20240601",
|
||||
"description": "Babel plugin for React Compiler.",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
@@ -63,10 +63,5 @@
|
||||
"@babel/core": "7.2.0",
|
||||
"@babel/generator": "7.2.0",
|
||||
"@babel/traverse": "7.7.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react.git",
|
||||
"directory": "compiler/packages/babel-plugin-react-compiler"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,29 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
import path from 'path';
|
||||
import process from 'process';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import prettier from 'rollup-plugin-prettier';
|
||||
import banner2 from 'rollup-plugin-banner2';
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import json from "@rollup/plugin-json";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
import prettier from "rollup-plugin-prettier";
|
||||
import banner2 from "rollup-plugin-banner2";
|
||||
|
||||
const NO_INLINE = new Set(['@babel/types']);
|
||||
const NO_INLINE = new Set(["@babel/types"]);
|
||||
|
||||
const DEV_ROLLUP_CONFIG = {
|
||||
input: 'src/index.ts',
|
||||
input: "src/index.ts",
|
||||
output: {
|
||||
file: 'dist/index.js',
|
||||
format: 'cjs',
|
||||
file: "dist/index.js",
|
||||
format: "cjs",
|
||||
sourcemap: false,
|
||||
exports: 'named',
|
||||
exports: "named",
|
||||
},
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: './tsconfig.json',
|
||||
tsconfig: "./tsconfig.json",
|
||||
compilerOptions: {
|
||||
noEmit: true,
|
||||
},
|
||||
@@ -35,8 +35,8 @@ const DEV_ROLLUP_CONFIG = {
|
||||
json(),
|
||||
nodeResolve({
|
||||
preferBuiltins: true,
|
||||
resolveOnly: module => NO_INLINE.has(module) === false,
|
||||
rootDir: path.join(process.cwd(), '..'),
|
||||
resolveOnly: (module) => NO_INLINE.has(module) === false,
|
||||
rootDir: path.join(process.cwd(), ".."),
|
||||
}),
|
||||
commonjs(),
|
||||
terser({
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import type * as BabelCore from "@babel/core";
|
||||
import { NodePath } from "@babel/core";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
export default function AnnotateReactCodeBabelPlugin(
|
||||
_babel: typeof BabelCore,
|
||||
_babel: typeof BabelCore
|
||||
): BabelCore.PluginObj {
|
||||
return {
|
||||
name: 'annotate-react-code',
|
||||
name: "annotate-react-code",
|
||||
visitor: {
|
||||
Program(prog): void {
|
||||
annotate(prog);
|
||||
@@ -56,23 +56,23 @@ function buildTypeOfReactForget(): t.Statement {
|
||||
// typeof globalThis[Symbol.for("react_forget")]
|
||||
return t.expressionStatement(
|
||||
t.unaryExpression(
|
||||
'typeof',
|
||||
"typeof",
|
||||
t.memberExpression(
|
||||
t.identifier('globalThis'),
|
||||
t.identifier("globalThis"),
|
||||
t.callExpression(
|
||||
t.memberExpression(
|
||||
t.identifier('Symbol'),
|
||||
t.identifier('for'),
|
||||
false,
|
||||
t.identifier("Symbol"),
|
||||
t.identifier("for"),
|
||||
false,
|
||||
false
|
||||
),
|
||||
[t.stringLiteral('react_forget')],
|
||||
[t.stringLiteral("react_forget")]
|
||||
),
|
||||
true,
|
||||
false,
|
||||
false
|
||||
),
|
||||
true,
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ type BabelFn =
|
||||
| NodePath<t.ArrowFunctionExpression>;
|
||||
|
||||
export function isComponentDeclaration(
|
||||
node: t.FunctionDeclaration,
|
||||
node: t.FunctionDeclaration
|
||||
): node is ComponentDeclaration {
|
||||
return Object.prototype.hasOwnProperty.call(node, '__componentDeclaration');
|
||||
return Object.prototype.hasOwnProperty.call(node, "__componentDeclaration");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -101,7 +101,7 @@ export function isComponentDeclaration(
|
||||
function isComponentOrHookLike(
|
||||
node: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
>
|
||||
): boolean {
|
||||
const functionName = getFunctionName(node);
|
||||
// Check if the name is component or hook like:
|
||||
@@ -114,7 +114,7 @@ function isComponentOrHookLike(
|
||||
* helpers are _usually_ named with lowercase, but some code may
|
||||
* violate this rule
|
||||
*/
|
||||
node.get('params').length <= 1
|
||||
node.get("params").length <= 1
|
||||
);
|
||||
} else if (functionName !== null && isHook(functionName)) {
|
||||
// Hooks have hook invocations or JSX, but can take any # of arguments
|
||||
@@ -151,11 +151,11 @@ function isHook(path: NodePath<t.Expression | t.PrivateName>): boolean {
|
||||
} else if (
|
||||
path.isMemberExpression() &&
|
||||
!path.node.computed &&
|
||||
isHook(path.get('property'))
|
||||
isHook(path.get("property"))
|
||||
) {
|
||||
const obj = path.get('object').node;
|
||||
const obj = path.get("object").node;
|
||||
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||
return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
|
||||
return obj.type === "Identifier" && isPascalCaseNameSpace.test(obj.name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -177,8 +177,8 @@ function isComponentName(path: NodePath<t.Expression>): boolean {
|
||||
function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
|
||||
return !!(
|
||||
path.parentPath.isCallExpression() &&
|
||||
path.parentPath.get('callee').isExpression() &&
|
||||
isReactAPI(path.parentPath.get('callee'), 'forwardRef')
|
||||
path.parentPath.get("callee").isExpression() &&
|
||||
isReactAPI(path.parentPath.get("callee"), "forwardRef")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,22 +190,22 @@ function isForwardRefCallback(path: NodePath<t.Expression>): boolean {
|
||||
function isMemoCallback(path: NodePath<t.Expression>): boolean {
|
||||
return (
|
||||
path.parentPath.isCallExpression() &&
|
||||
path.parentPath.get('callee').isExpression() &&
|
||||
isReactAPI(path.parentPath.get('callee'), 'memo')
|
||||
path.parentPath.get("callee").isExpression() &&
|
||||
isReactAPI(path.parentPath.get("callee"), "memo")
|
||||
);
|
||||
}
|
||||
|
||||
function isReactAPI(
|
||||
path: NodePath<t.Expression | t.PrivateName | t.V8IntrinsicIdentifier>,
|
||||
functionName: string,
|
||||
functionName: string
|
||||
): boolean {
|
||||
const node = path.node;
|
||||
return (
|
||||
(node.type === 'Identifier' && node.name === functionName) ||
|
||||
(node.type === 'MemberExpression' &&
|
||||
node.object.type === 'Identifier' &&
|
||||
node.object.name === 'React' &&
|
||||
node.property.type === 'Identifier' &&
|
||||
(node.type === "Identifier" && node.name === functionName) ||
|
||||
(node.type === "MemberExpression" &&
|
||||
node.object.type === "Identifier" &&
|
||||
node.object.name === "React" &&
|
||||
node.property.type === "Identifier" &&
|
||||
node.property.name === functionName)
|
||||
);
|
||||
}
|
||||
@@ -218,7 +218,7 @@ function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
|
||||
createsJsx = true;
|
||||
},
|
||||
CallExpression(call) {
|
||||
const callee = call.get('callee');
|
||||
const callee = call.get("callee");
|
||||
if (callee.isExpression() && isHook(callee)) {
|
||||
invokesHooks = true;
|
||||
}
|
||||
@@ -239,10 +239,10 @@ function callsHooksOrCreatesJsx(node: NodePath<t.Node>): boolean {
|
||||
function getFunctionName(
|
||||
path: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
>
|
||||
): NodePath<t.Expression> | null {
|
||||
if (path.isFunctionDeclaration()) {
|
||||
const id = path.get('id');
|
||||
const id = path.get("id");
|
||||
if (id.isIdentifier()) {
|
||||
return id;
|
||||
}
|
||||
@@ -250,31 +250,31 @@ function getFunctionName(
|
||||
}
|
||||
let id: NodePath<t.LVal | t.Expression | t.PrivateName> | null = null;
|
||||
const parent = path.parentPath;
|
||||
if (parent.isVariableDeclarator() && parent.get('init').node === path.node) {
|
||||
if (parent.isVariableDeclarator() && parent.get("init").node === path.node) {
|
||||
// const useHook = () => {};
|
||||
id = parent.get('id');
|
||||
id = parent.get("id");
|
||||
} else if (
|
||||
parent.isAssignmentExpression() &&
|
||||
parent.get('right').node === path.node &&
|
||||
parent.get('operator') === '='
|
||||
parent.get("right").node === path.node &&
|
||||
parent.get("operator") === "="
|
||||
) {
|
||||
// useHook = () => {};
|
||||
id = parent.get('left');
|
||||
id = parent.get("left");
|
||||
} else if (
|
||||
parent.isProperty() &&
|
||||
parent.get('value').node === path.node &&
|
||||
!parent.get('computed') &&
|
||||
parent.get('key').isLVal()
|
||||
parent.get("value").node === path.node &&
|
||||
!parent.get("computed") &&
|
||||
parent.get("key").isLVal()
|
||||
) {
|
||||
/*
|
||||
* {useHook: () => {}}
|
||||
* {useHook() {}}
|
||||
*/
|
||||
id = parent.get('key');
|
||||
id = parent.get("key");
|
||||
} else if (
|
||||
parent.isAssignmentPattern() &&
|
||||
parent.get('right').node === path.node &&
|
||||
!parent.get('computed')
|
||||
parent.get("right").node === path.node &&
|
||||
!parent.get("computed")
|
||||
) {
|
||||
/*
|
||||
* const {useHook = () => {}} = {};
|
||||
@@ -283,7 +283,7 @@ function getFunctionName(
|
||||
* Kinda clowny, but we'd said we'd follow spec convention for
|
||||
* `IsAnonymousFunctionDefinition()` usage.
|
||||
*/
|
||||
id = parent.get('left');
|
||||
id = parent.get("left");
|
||||
}
|
||||
if (id !== null && (id.isIdentifier() || id.isMemberExpression())) {
|
||||
return id;
|
||||
|
||||
@@ -5,27 +5,27 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const {tests} = require('./eslint-plugin-react-hooks-test-cases');
|
||||
const { tests } = require("./eslint-plugin-react-hooks-test-cases");
|
||||
const {
|
||||
runBabelPluginReactCompiler,
|
||||
} = require('../dist/Babel/RunReactCompilerBabelPlugin');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const prettier = require('prettier');
|
||||
const prettierConfigPath = require.resolve('../.prettierrc');
|
||||
const process = require('process');
|
||||
const {createHash} = require('crypto');
|
||||
const {create} = require('domain');
|
||||
} = require("../dist/Babel/RunReactCompilerBabelPlugin");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const prettier = require("prettier");
|
||||
const prettierConfigPath = require.resolve("../.prettierrc");
|
||||
const process = require("process");
|
||||
const { createHash } = require("crypto");
|
||||
const { create } = require("domain");
|
||||
|
||||
const FIXTURES_DIR = path.join(
|
||||
process.cwd(),
|
||||
'src',
|
||||
'__tests__',
|
||||
'fixtures',
|
||||
'compiler',
|
||||
'rules-of-hooks'
|
||||
"src",
|
||||
"__tests__",
|
||||
"fixtures",
|
||||
"compiler",
|
||||
"rules-of-hooks"
|
||||
);
|
||||
|
||||
const PRETTIER_OPTIONS = prettier.resolveConfig.sync(FIXTURES_DIR, {
|
||||
@@ -34,10 +34,10 @@ const PRETTIER_OPTIONS = prettier.resolveConfig.sync(FIXTURES_DIR, {
|
||||
|
||||
const fixtures = [];
|
||||
for (const test of tests.valid) {
|
||||
fixtures.push({code: test.code, valid: true});
|
||||
fixtures.push({ code: test.code, valid: true });
|
||||
}
|
||||
for (const test of tests.invalid) {
|
||||
fixtures.push({code: test.code, valid: false});
|
||||
fixtures.push({ code: test.code, valid: false });
|
||||
}
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
@@ -47,8 +47,8 @@ for (const fixture of fixtures) {
|
||||
// Does the fixture pass with hooks validation disabled? if not skip it
|
||||
runBabelPluginReactCompiler(
|
||||
fixture.code,
|
||||
'rules-of-hooks.js',
|
||||
'typescript',
|
||||
"rules-of-hooks.js",
|
||||
"typescript",
|
||||
{
|
||||
environment: {
|
||||
validateHooksUsage: false,
|
||||
@@ -59,8 +59,8 @@ for (const fixture of fixtures) {
|
||||
try {
|
||||
runBabelPluginReactCompiler(
|
||||
fixture.code,
|
||||
'rules-of-hooks.js',
|
||||
'typescript',
|
||||
"rules-of-hooks.js",
|
||||
"typescript",
|
||||
{
|
||||
environment: {
|
||||
validateHooksUsage: true,
|
||||
@@ -74,7 +74,7 @@ for (const fixture of fixtures) {
|
||||
error = e;
|
||||
}
|
||||
let code = fixture.code;
|
||||
let prefix = '';
|
||||
let prefix = "";
|
||||
if (error !== null) {
|
||||
prefix = `todo.bail.`;
|
||||
code = `// @skip\n// Unsupported input\n${code}`;
|
||||
@@ -92,11 +92,11 @@ for (const fixture of fixtures) {
|
||||
code = `// @skip\n// Failed but should have passed\n${code}`;
|
||||
}
|
||||
const formatted = prettier.format(code, PRETTIER_OPTIONS);
|
||||
const hmac = createHash('sha256');
|
||||
hmac.update(formatted, 'utf8');
|
||||
const hmac = createHash("sha256");
|
||||
hmac.update(formatted, "utf8");
|
||||
let name = `${prefix}rules-of-hooks-${hmac
|
||||
.digest('hex')
|
||||
.digest("hex")
|
||||
.substring(0, 12)}.js`;
|
||||
const fixturePath = path.join(FIXTURES_DIR, name);
|
||||
fs.writeFileSync(fixturePath, formatted, 'utf8');
|
||||
fs.writeFileSync(fixturePath, formatted, "utf8");
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
// NOTE: Extracted from https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* A string template tag that removes padding from the left side of multi-line strings
|
||||
*/
|
||||
function normalizeIndent(strings) {
|
||||
const codeLines = strings[0].split('\n');
|
||||
const codeLines = strings[0].split("\n");
|
||||
const leftPadding = codeLines[1].match(/\s+/)[0];
|
||||
return codeLines.map(line => line.slice(leftPadding.length)).join('\n');
|
||||
return codeLines.map((line) => line.slice(leftPadding.length)).join("\n");
|
||||
}
|
||||
|
||||
module.exports.tests = {
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeE2EConfig = require('../jest/makeE2EConfig');
|
||||
const makeE2EConfig = require("../jest/makeE2EConfig");
|
||||
|
||||
module.exports = makeE2EConfig('e2e no forget', false);
|
||||
module.exports = makeE2EConfig("e2e no forget", false);
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeE2EConfig = require('../jest/makeE2EConfig');
|
||||
const makeE2EConfig = require("../jest/makeE2EConfig");
|
||||
|
||||
const config = makeE2EConfig('e2e with forget', true);
|
||||
config.setupFilesAfterEnv = ['<rootDir>/../scripts/jest/setupEnvE2E.js'];
|
||||
const config = makeE2EConfig("e2e with forget", true);
|
||||
config.setupFilesAfterEnv = ["<rootDir>/../scripts/jest/setupEnvE2E.js"];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
displayName: 'main',
|
||||
preset: 'ts-jest',
|
||||
rootDir: '../../src',
|
||||
testPathIgnorePatterns: ['e2e', 'TestDriver', 'test-utils', 'fixtures'],
|
||||
displayName: "main",
|
||||
preset: "ts-jest",
|
||||
rootDir: "../../src",
|
||||
testPathIgnorePatterns: ["e2e", "TestDriver", "test-utils", "fixtures"],
|
||||
globals: {
|
||||
__DEV__: true,
|
||||
},
|
||||
|
||||
@@ -8,27 +8,27 @@
|
||||
module.exports = function makeE2EConfig(displayName, useForget) {
|
||||
return {
|
||||
displayName,
|
||||
testEnvironment: 'jsdom',
|
||||
rootDir: '../../src',
|
||||
testMatch: ['**/*.e2e.(js|tsx)'],
|
||||
testEnvironment: "jsdom",
|
||||
rootDir: "../../src",
|
||||
testMatch: ["**/*.e2e.(js|tsx)"],
|
||||
modulePathIgnorePatterns: [
|
||||
// ignore snapshots from the opposite forget configuration
|
||||
useForget ? '.*\\.no-forget\\.snap$' : '.*\\.with-forget\\.snap$',
|
||||
useForget ? ".*\\.no-forget\\.snap$" : ".*\\.with-forget\\.snap$",
|
||||
// ignore snapshots from the main project
|
||||
'.*\\.ts\\.snap$',
|
||||
".*\\.ts\\.snap$",
|
||||
],
|
||||
globals: {
|
||||
__FORGET__: useForget,
|
||||
},
|
||||
snapshotResolver: useForget
|
||||
? '<rootDir>/../scripts/jest/snapshot-resolver-with-forget.js'
|
||||
: '<rootDir>/../scripts/jest/snapshot-resolver-no-forget.js',
|
||||
? "<rootDir>/../scripts/jest/snapshot-resolver-with-forget.js"
|
||||
: "<rootDir>/../scripts/jest/snapshot-resolver-no-forget.js",
|
||||
|
||||
transform: {
|
||||
'\\.[tj]sx?$': useForget
|
||||
? '<rootDir>/../scripts/jest/transform-with-forget'
|
||||
: '<rootDir>/../scripts/jest/transform-no-forget',
|
||||
"\\.[tj]sx?$": useForget
|
||||
? "<rootDir>/../scripts/jest/transform-with-forget"
|
||||
: "<rootDir>/../scripts/jest/transform-no-forget",
|
||||
},
|
||||
transformIgnorePatterns: ['/node_modules/'],
|
||||
transformIgnorePatterns: ["/node_modules/"],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
module.exports = function makeSnapshotResolver(useForget) {
|
||||
const modeExtension = useForget ? '.with-forget' : '.no-forget';
|
||||
const modeExtension = useForget ? ".with-forget" : ".no-forget";
|
||||
return {
|
||||
resolveSnapshotPath: (testPath, snapshotExtension) =>
|
||||
testPath + modeExtension + snapshotExtension,
|
||||
@@ -17,6 +17,6 @@ module.exports = function makeSnapshotResolver(useForget) {
|
||||
-modeExtension.length - snapshotExtension.length
|
||||
),
|
||||
|
||||
testPathForConsistencyCheck: 'some/__tests__/example.test.js',
|
||||
testPathForConsistencyCheck: "some/__tests__/example.test.js",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,19 +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 {compile} from 'babel-plugin-react-compiler';
|
||||
import {execSync} from 'child_process';
|
||||
import { jsx } from "@babel/plugin-syntax-jsx";
|
||||
import babelJest from "babel-jest";
|
||||
import { compile } from "babel-plugin-react-compiler";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
import type {NodePath, Visitor} from '@babel/traverse';
|
||||
import type {CallExpression, FunctionDeclaration} from '@babel/types';
|
||||
import * as t from '@babel/types';
|
||||
import type { NodePath, Visitor } from "@babel/traverse";
|
||||
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';
|
||||
} from "babel-plugin-react-compiler";
|
||||
import { basename } from "path";
|
||||
|
||||
/**
|
||||
* -- IMPORTANT --
|
||||
@@ -28,16 +28,15 @@ import {basename} from 'path';
|
||||
const e2eTransformerCacheKey = 1;
|
||||
const forgetOptions: EnvironmentConfig = validateEnvironmentConfig({
|
||||
enableAssumeHooksFollowRulesOfReact: true,
|
||||
enableFunctionOutlining: false,
|
||||
});
|
||||
const debugMode = process.env['DEBUG_FORGET_COMPILER'] != null;
|
||||
const debugMode = process.env["DEBUG_FORGET_COMPILER"] != null;
|
||||
|
||||
module.exports = (useForget: boolean) => {
|
||||
function createTransformer() {
|
||||
return babelJest.createTransformer({
|
||||
passPerPreset: true,
|
||||
presets: [
|
||||
'@babel/preset-typescript',
|
||||
"@babel/preset-typescript",
|
||||
{
|
||||
plugins: [
|
||||
useForget
|
||||
@@ -49,36 +48,36 @@ module.exports = (useForget: boolean) => {
|
||||
* (see https://github.com/jestjs/jest/blob/v29.6.2/packages/babel-jest/src/index.ts#L84)
|
||||
*/
|
||||
compilerCacheKey: execSync(
|
||||
'yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist',
|
||||
"yarn --silent --cwd ../.. hash packages/babel-plugin-react-compiler/dist"
|
||||
).toString(),
|
||||
transformOptionsCacheKey: forgetOptions,
|
||||
e2eTransformerCacheKey,
|
||||
},
|
||||
]
|
||||
: '@babel/plugin-syntax-jsx',
|
||||
: "@babel/plugin-syntax-jsx",
|
||||
],
|
||||
},
|
||||
'@babel/preset-react',
|
||||
"@babel/preset-react",
|
||||
{
|
||||
plugins: [
|
||||
[
|
||||
function BabelPluginRewriteRequirePath(): {visitor: Visitor} {
|
||||
function BabelPluginRewriteRequirePath(): { visitor: Visitor } {
|
||||
return {
|
||||
visitor: {
|
||||
CallExpression(path: NodePath<CallExpression>): void {
|
||||
const {callee} = path.node;
|
||||
const { callee } = path.node;
|
||||
if (
|
||||
callee.type === 'Identifier' &&
|
||||
callee.name === 'require'
|
||||
callee.type === "Identifier" &&
|
||||
callee.name === "require"
|
||||
) {
|
||||
const arg = path.node.arguments[0];
|
||||
if (arg.type === 'StringLiteral') {
|
||||
if (arg.type === "StringLiteral") {
|
||||
/*
|
||||
* The compiler adds requires of "React", which is expected to be a wrapper
|
||||
* around the "react" package. For tests, we just rewrite the require.
|
||||
*/
|
||||
if (arg.value === 'React') {
|
||||
arg.value = 'react';
|
||||
if (arg.value === "React") {
|
||||
arg.value = "react";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +86,7 @@ module.exports = (useForget: boolean) => {
|
||||
};
|
||||
},
|
||||
],
|
||||
'@babel/plugin-transform-modules-commonjs',
|
||||
"@babel/plugin-transform-modules-commonjs",
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -125,7 +124,7 @@ function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
|
||||
|
||||
fn.traverse({
|
||||
DirectiveLiteral(path) {
|
||||
if (path.node.value === 'use no forget') {
|
||||
if (path.node.value === "use no forget") {
|
||||
hasNoUseForgetDirective = true;
|
||||
}
|
||||
},
|
||||
@@ -140,7 +139,7 @@ function isReactComponentLike(fn: NodePath<FunctionDeclaration>): boolean {
|
||||
CallExpression(path) {
|
||||
// Is there hook usage?
|
||||
if (
|
||||
path.node.callee.type === 'Identifier' &&
|
||||
path.node.callee.type === "Identifier" &&
|
||||
!/^use[A-Z0-9]/.test(path.node.callee.name)
|
||||
) {
|
||||
isReactComponent = true;
|
||||
@@ -170,7 +169,7 @@ function ReactForgetFunctionTransform() {
|
||||
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}`,
|
||||
` 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}`);
|
||||
@@ -180,11 +179,11 @@ function ReactForgetFunctionTransform() {
|
||||
const compiled = compile(
|
||||
fn,
|
||||
forgetOptions,
|
||||
'Other',
|
||||
'_c',
|
||||
null,
|
||||
"Other",
|
||||
"_c",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
compiledFns.add(compiled);
|
||||
|
||||
@@ -193,14 +192,14 @@ function ReactForgetFunctionTransform() {
|
||||
compiled.params,
|
||||
compiled.body,
|
||||
compiled.generator,
|
||||
compiled.async,
|
||||
compiled.async
|
||||
);
|
||||
fn.replaceWith(fun);
|
||||
fn.skip();
|
||||
},
|
||||
};
|
||||
return {
|
||||
name: 'react-forget-e2e',
|
||||
name: "react-forget-e2e",
|
||||
inherits: jsx,
|
||||
visitor,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const ReactCompilerRuntime = require('react/compiler-runtime');
|
||||
const ReactCompilerRuntime = require("react/compiler-runtime");
|
||||
|
||||
/*
|
||||
* Our e2e babel transform currently only compiles functions, not programs.
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeSnapshotResolver = require('./makeSnapshotResolver');
|
||||
const makeSnapshotResolver = require("./makeSnapshotResolver");
|
||||
|
||||
module.exports = makeSnapshotResolver(false);
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const makeSnapshotResolver = require('./makeSnapshotResolver');
|
||||
const makeSnapshotResolver = require("./makeSnapshotResolver");
|
||||
|
||||
module.exports = makeSnapshotResolver(true);
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
module.exports = require('./makeTransform')(false);
|
||||
module.exports = require("./makeTransform")(false);
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
module.exports = require('./makeTransform')(true);
|
||||
module.exports = require("./makeTransform")(true);
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {compileProgram, parsePluginOptions} from '../Entrypoint';
|
||||
import type * as BabelCore from "@babel/core";
|
||||
import { compileProgram, parsePluginOptions } from "../Entrypoint";
|
||||
import {
|
||||
injectReanimatedFlag,
|
||||
pipelineUsesReanimatedPlugin,
|
||||
} from '../Entrypoint/Reanimated';
|
||||
} from "../Entrypoint/Reanimated";
|
||||
|
||||
/*
|
||||
* The React Forget Babel Plugin
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
* @returns
|
||||
*/
|
||||
export default function BabelPluginReactCompiler(
|
||||
_babel: typeof BabelCore,
|
||||
_babel: typeof BabelCore
|
||||
): BabelCore.PluginObj {
|
||||
return {
|
||||
name: 'react-forget',
|
||||
name: "react-forget",
|
||||
visitor: {
|
||||
/*
|
||||
* Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
|
||||
@@ -30,16 +30,13 @@ export default function BabelPluginReactCompiler(
|
||||
*/
|
||||
Program(prog, pass): void {
|
||||
let opts = parsePluginOptions(pass.opts);
|
||||
const isDev =
|
||||
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
|
||||
process.env['NODE_ENV'] === 'development';
|
||||
if (
|
||||
opts.enableReanimatedCheck === true &&
|
||||
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
|
||||
) {
|
||||
opts = injectReanimatedFlag(opts);
|
||||
}
|
||||
if (isDev) {
|
||||
if (process.env["NODE_ENV"] === "development") {
|
||||
opts = {
|
||||
...opts,
|
||||
environment: {
|
||||
|
||||
@@ -5,25 +5,25 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {transformFromAstSync} from '@babel/core';
|
||||
import * as BabelParser from '@babel/parser';
|
||||
import invariant from 'invariant';
|
||||
import type {PluginOptions} from '../Entrypoint';
|
||||
import BabelPluginReactCompiler from './BabelPlugin';
|
||||
import type * as BabelCore from "@babel/core";
|
||||
import { transformFromAstSync } from "@babel/core";
|
||||
import * as BabelParser from "@babel/parser";
|
||||
import invariant from "invariant";
|
||||
import type { PluginOptions } from "../Entrypoint";
|
||||
import BabelPluginReactCompiler from "./BabelPlugin";
|
||||
|
||||
export const DEFAULT_PLUGINS = ['babel-plugin-fbt', 'babel-plugin-fbt-runtime'];
|
||||
export const DEFAULT_PLUGINS = ["babel-plugin-fbt", "babel-plugin-fbt-runtime"];
|
||||
export function runBabelPluginReactCompiler(
|
||||
text: string,
|
||||
file: string,
|
||||
language: 'flow' | 'typescript',
|
||||
language: "flow" | "typescript",
|
||||
options: Partial<PluginOptions> | null,
|
||||
includeAst: boolean = false,
|
||||
includeAst: boolean = false
|
||||
): BabelCore.BabelFileResult {
|
||||
const ast = BabelParser.parse(text, {
|
||||
sourceFilename: file,
|
||||
plugins: [language, 'jsx'],
|
||||
sourceType: 'module',
|
||||
plugins: [language, "jsx"],
|
||||
sourceType: "module",
|
||||
});
|
||||
const result = transformFromAstSync(ast, text, {
|
||||
ast: includeAst,
|
||||
@@ -32,16 +32,16 @@ export function runBabelPluginReactCompiler(
|
||||
retainLines: true,
|
||||
plugins: [
|
||||
[BabelPluginReactCompiler, options],
|
||||
'babel-plugin-fbt',
|
||||
'babel-plugin-fbt-runtime',
|
||||
"babel-plugin-fbt",
|
||||
"babel-plugin-fbt-runtime",
|
||||
],
|
||||
sourceType: 'module',
|
||||
sourceType: "module",
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
});
|
||||
invariant(
|
||||
result?.code != null,
|
||||
`Expected BabelPluginReactForget to codegen successfully, got: ${result}`,
|
||||
`Expected BabelPluginReactForget to codegen successfully, got: ${result}`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5,37 +5,37 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {SourceLocation} from './HIR';
|
||||
import {assertExhaustive} from './Utils/utils';
|
||||
import type { SourceLocation } from "./HIR";
|
||||
import { assertExhaustive } from "./Utils/utils";
|
||||
|
||||
export enum ErrorSeverity {
|
||||
/**
|
||||
* Invalid JS syntax, or valid syntax that is semantically invalid which may indicate some
|
||||
* misunderstanding on the user’s part.
|
||||
*/
|
||||
InvalidJS = 'InvalidJS',
|
||||
InvalidJS = "InvalidJS",
|
||||
/**
|
||||
* Code that breaks the rules of React.
|
||||
*/
|
||||
InvalidReact = 'InvalidReact',
|
||||
InvalidReact = "InvalidReact",
|
||||
/**
|
||||
* Incorrect configuration of the compiler.
|
||||
*/
|
||||
InvalidConfig = 'InvalidConfig',
|
||||
InvalidConfig = "InvalidConfig",
|
||||
/**
|
||||
* Code that can reasonably occur and that doesn't break any rules, but is unsafe to preserve
|
||||
* memoization.
|
||||
*/
|
||||
CannotPreserveMemoization = 'CannotPreserveMemoization',
|
||||
CannotPreserveMemoization = "CannotPreserveMemoization",
|
||||
/**
|
||||
* Unhandled syntax that we don't support yet.
|
||||
*/
|
||||
Todo = 'Todo',
|
||||
Todo = "Todo",
|
||||
/**
|
||||
* An unexpected internal error in the compiler that indicates critical issues that can panic
|
||||
* the compiler.
|
||||
*/
|
||||
Invariant = 'Invariant',
|
||||
Invariant = "Invariant",
|
||||
}
|
||||
|
||||
export enum CompilerSuggestionOperation {
|
||||
@@ -79,19 +79,19 @@ export class CompilerErrorDetail {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
get reason(): CompilerErrorDetailOptions['reason'] {
|
||||
get reason(): CompilerErrorDetailOptions["reason"] {
|
||||
return this.options.reason;
|
||||
}
|
||||
get description(): CompilerErrorDetailOptions['description'] {
|
||||
get description(): CompilerErrorDetailOptions["description"] {
|
||||
return this.options.description;
|
||||
}
|
||||
get severity(): CompilerErrorDetailOptions['severity'] {
|
||||
get severity(): CompilerErrorDetailOptions["severity"] {
|
||||
return this.options.severity;
|
||||
}
|
||||
get loc(): CompilerErrorDetailOptions['loc'] {
|
||||
get loc(): CompilerErrorDetailOptions["loc"] {
|
||||
return this.options.loc;
|
||||
}
|
||||
get suggestions(): CompilerErrorDetailOptions['suggestions'] {
|
||||
get suggestions(): CompilerErrorDetailOptions["suggestions"] {
|
||||
return this.options.suggestions;
|
||||
}
|
||||
|
||||
@@ -100,10 +100,10 @@ export class CompilerErrorDetail {
|
||||
if (this.description != null) {
|
||||
buffer.push(`. ${this.description}`);
|
||||
}
|
||||
if (this.loc != null && typeof this.loc !== 'symbol') {
|
||||
if (this.loc != null && typeof this.loc !== "symbol") {
|
||||
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
|
||||
}
|
||||
return buffer.join('');
|
||||
return buffer.join("");
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
@@ -116,7 +116,7 @@ export class CompilerError extends Error {
|
||||
|
||||
static invariant(
|
||||
condition: unknown,
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): asserts condition {
|
||||
if (!condition) {
|
||||
const errors = new CompilerError();
|
||||
@@ -124,57 +124,57 @@ export class CompilerError extends Error {
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
|
||||
static throwTodo(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({...options, severity: ErrorSeverity.Todo}),
|
||||
new CompilerErrorDetail({ ...options, severity: ErrorSeverity.Todo })
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidJS(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidJS,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidReact(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
|
||||
static throwInvalidConfig(
|
||||
options: Omit<CompilerErrorDetailOptions, 'severity'>,
|
||||
options: Omit<CompilerErrorDetailOptions, "severity">
|
||||
): never {
|
||||
const errors = new CompilerError();
|
||||
errors.pushErrorDetail(
|
||||
new CompilerErrorDetail({
|
||||
...options,
|
||||
severity: ErrorSeverity.InvalidConfig,
|
||||
}),
|
||||
})
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ export class CompilerError extends Error {
|
||||
|
||||
constructor(...args: Array<any>) {
|
||||
super(...args);
|
||||
this.name = 'ReactCompilerError';
|
||||
this.name = "ReactCompilerError";
|
||||
}
|
||||
|
||||
override get message(): string {
|
||||
@@ -197,7 +197,7 @@ export class CompilerError extends Error {
|
||||
override set message(_message: string) {}
|
||||
|
||||
override toString(): string {
|
||||
return this.details.map(detail => detail.toString()).join('\n\n');
|
||||
return this.details.map((detail) => detail.toString()).join("\n\n");
|
||||
}
|
||||
|
||||
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
|
||||
@@ -206,7 +206,7 @@ export class CompilerError extends Error {
|
||||
description: options.description ?? null,
|
||||
severity: options.severity,
|
||||
suggestions: options.suggestions,
|
||||
loc: typeof options.loc === 'symbol' ? null : options.loc,
|
||||
loc: typeof options.loc === "symbol" ? null : options.loc,
|
||||
});
|
||||
return this.pushErrorDetail(detail);
|
||||
}
|
||||
@@ -226,7 +226,7 @@ export class CompilerError extends Error {
|
||||
* but otherwise continue compiling the rest of the app.
|
||||
*/
|
||||
isCritical(): boolean {
|
||||
return this.details.some(detail => {
|
||||
return this.details.some((detail) => {
|
||||
switch (detail.severity) {
|
||||
case ErrorSeverity.Invariant:
|
||||
case ErrorSeverity.InvalidJS:
|
||||
@@ -237,7 +237,7 @@ export class CompilerError extends Error {
|
||||
case ErrorSeverity.Todo:
|
||||
return false;
|
||||
default:
|
||||
assertExhaustive(detail.severity, 'Unhandled error severity');
|
||||
assertExhaustive(detail.severity, "Unhandled error severity");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import {PluginOptions} from './Options';
|
||||
import { NodePath } from "@babel/core";
|
||||
import * as t from "@babel/types";
|
||||
import { PluginOptions } from "./Options";
|
||||
|
||||
export function insertGatedFunctionDeclaration(
|
||||
fnPath: NodePath<
|
||||
@@ -17,14 +17,15 @@ export function insertGatedFunctionDeclaration(
|
||||
| t.FunctionDeclaration
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression,
|
||||
gating: NonNullable<PluginOptions['gating']>,
|
||||
): void {
|
||||
gating: NonNullable<PluginOptions["gating"]>
|
||||
): NodePath<t.ConditionalExpression | t.VariableDeclaration> {
|
||||
const gatingExpression = t.conditionalExpression(
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
buildFunctionExpression(compiled),
|
||||
buildFunctionExpression(fnPath.node),
|
||||
buildFunctionExpression(fnPath.node)
|
||||
);
|
||||
|
||||
let compiledFn;
|
||||
/*
|
||||
* Convert function declarations to named variables *unless* this is an
|
||||
* `export default function ...` since `export default const ...` is
|
||||
@@ -32,50 +33,33 @@ export function insertGatedFunctionDeclaration(
|
||||
* conditional expression
|
||||
*/
|
||||
if (
|
||||
fnPath.parentPath.node.type !== 'ExportDefaultDeclaration' &&
|
||||
fnPath.node.type === 'FunctionDeclaration' &&
|
||||
fnPath.parentPath.node.type !== "ExportDefaultDeclaration" &&
|
||||
fnPath.node.type === "FunctionDeclaration" &&
|
||||
fnPath.node.id != null
|
||||
) {
|
||||
fnPath.replaceWith(
|
||||
t.variableDeclaration('const', [
|
||||
compiledFn = 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,
|
||||
),
|
||||
]),
|
||||
);
|
||||
])
|
||||
)[0];
|
||||
} else {
|
||||
fnPath.replaceWith(gatingExpression);
|
||||
compiledFn = fnPath.replaceWith(gatingExpression)[0];
|
||||
}
|
||||
|
||||
return compiledFn;
|
||||
}
|
||||
|
||||
function buildFunctionExpression(
|
||||
node:
|
||||
| t.FunctionDeclaration
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression,
|
||||
node: t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
): t.ArrowFunctionExpression | t.FunctionExpression {
|
||||
if (
|
||||
node.type === 'ArrowFunctionExpression' ||
|
||||
node.type === 'FunctionExpression'
|
||||
node.type === "ArrowFunctionExpression" ||
|
||||
node.type === "FunctionExpression"
|
||||
) {
|
||||
return node;
|
||||
} else {
|
||||
const fn: t.FunctionExpression = {
|
||||
type: 'FunctionExpression',
|
||||
type: "FunctionExpression",
|
||||
async: node.async,
|
||||
generator: node.generator,
|
||||
loc: node.loc ?? null,
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {ExternalFunction, GeneratedSource} from '../HIR';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
import { NodePath } from "@babel/core";
|
||||
import * as t from "@babel/types";
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { ExternalFunction, GeneratedSource } from "../HIR";
|
||||
import { getOrInsertDefault } from "../Utils/utils";
|
||||
|
||||
export function addImportsToProgram(
|
||||
path: NodePath<t.Program>,
|
||||
importList: Array<ExternalFunction>,
|
||||
importList: Array<ExternalFunction>
|
||||
): void {
|
||||
const identifiers: Set<string> = new Set();
|
||||
const sortedImports: Map<string, Array<string>> = new Map();
|
||||
for (const {importSpecifierName, source} of importList) {
|
||||
for (const { importSpecifierName, source } of importList) {
|
||||
/*
|
||||
* Codegen currently does not rename import specifiers, so we do additional
|
||||
* validation here
|
||||
@@ -35,28 +35,28 @@ export function addImportsToProgram(
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
identifiers.add(importSpecifierName);
|
||||
|
||||
const importSpecifierNameList = getOrInsertDefault(
|
||||
sortedImports,
|
||||
source,
|
||||
[],
|
||||
[]
|
||||
);
|
||||
importSpecifierNameList.push(importSpecifierName);
|
||||
}
|
||||
|
||||
const stmts: Array<t.ImportDeclaration> = [];
|
||||
for (const [source, importSpecifierNameList] of sortedImports) {
|
||||
const importSpecifiers = importSpecifierNameList.map(name => {
|
||||
const importSpecifiers = importSpecifierNameList.map((name) => {
|
||||
const id = t.identifier(name);
|
||||
return t.importSpecifier(id, id);
|
||||
});
|
||||
|
||||
stmts.push(t.importDeclaration(importSpecifiers, t.stringLiteral(source)));
|
||||
}
|
||||
path.unshiftContainer('body', stmts);
|
||||
path.unshiftContainer("body", stmts);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -65,21 +65,21 @@ export function addImportsToProgram(
|
||||
*/
|
||||
function isNonNamespacedImport(
|
||||
importDeclPath: NodePath<t.ImportDeclaration>,
|
||||
moduleName: string,
|
||||
moduleName: string
|
||||
): boolean {
|
||||
return (
|
||||
importDeclPath.get('source').node.value === moduleName &&
|
||||
importDeclPath.get("source").node.value === moduleName &&
|
||||
importDeclPath
|
||||
.get('specifiers')
|
||||
.every(specifier => specifier.isImportSpecifier()) &&
|
||||
importDeclPath.node.importKind !== 'type' &&
|
||||
importDeclPath.node.importKind !== 'typeof'
|
||||
.get("specifiers")
|
||||
.every((specifier) => specifier.isImportSpecifier()) &&
|
||||
importDeclPath.node.importKind !== "type" &&
|
||||
importDeclPath.node.importKind !== "typeof"
|
||||
);
|
||||
}
|
||||
|
||||
function hasExistingNonNamespacedImportOfModule(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
moduleName: string
|
||||
): boolean {
|
||||
let hasExistingImport = false;
|
||||
program.traverse({
|
||||
@@ -100,7 +100,7 @@ function hasExistingNonNamespacedImportOfModule(
|
||||
function addMemoCacheFunctionSpecifierToExistingImport(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
identifierName: string,
|
||||
identifierName: string
|
||||
): boolean {
|
||||
let didInsertUseMemoCache = false;
|
||||
program.traverse({
|
||||
@@ -110,8 +110,8 @@ function addMemoCacheFunctionSpecifierToExistingImport(
|
||||
isNonNamespacedImport(importDeclPath, moduleName)
|
||||
) {
|
||||
importDeclPath.pushContainer(
|
||||
'specifiers',
|
||||
t.importSpecifier(t.identifier(identifierName), t.identifier('c')),
|
||||
"specifiers",
|
||||
t.importSpecifier(t.identifier(identifierName), t.identifier("c"))
|
||||
);
|
||||
didInsertUseMemoCache = true;
|
||||
}
|
||||
@@ -123,7 +123,7 @@ function addMemoCacheFunctionSpecifierToExistingImport(
|
||||
export function updateMemoCacheFunctionImport(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
useMemoCacheIdentifier: string,
|
||||
useMemoCacheIdentifier: string
|
||||
): void {
|
||||
/*
|
||||
* If there isn't already an import of * as React, insert it so useMemoCache doesn't
|
||||
@@ -131,25 +131,25 @@ export function updateMemoCacheFunctionImport(
|
||||
*/
|
||||
const hasExistingImport = hasExistingNonNamespacedImportOfModule(
|
||||
program,
|
||||
moduleName,
|
||||
moduleName
|
||||
);
|
||||
|
||||
if (hasExistingImport) {
|
||||
const didUpdateImport = addMemoCacheFunctionSpecifierToExistingImport(
|
||||
program,
|
||||
moduleName,
|
||||
useMemoCacheIdentifier,
|
||||
useMemoCacheIdentifier
|
||||
);
|
||||
if (!didUpdateImport) {
|
||||
throw new Error(
|
||||
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`,
|
||||
`Expected an ImportDeclaration of \`${moduleName}\` in order to update ImportSpecifiers with useMemoCache`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addMemoCacheFunctionImportDeclaration(
|
||||
program,
|
||||
moduleName,
|
||||
useMemoCacheIdentifier,
|
||||
useMemoCacheIdentifier
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -157,13 +157,13 @@ export function updateMemoCacheFunctionImport(
|
||||
function addMemoCacheFunctionImportDeclaration(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string,
|
||||
localName: string,
|
||||
localName: string
|
||||
): void {
|
||||
program.unshiftContainer(
|
||||
'body',
|
||||
"body",
|
||||
t.importDeclaration(
|
||||
[t.importSpecifier(t.identifier(localName), t.identifier('c'))],
|
||||
t.stringLiteral(moduleName),
|
||||
),
|
||||
[t.importSpecifier(t.identifier(localName), t.identifier("c"))],
|
||||
t.stringLiteral(moduleName)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as t from '@babel/types';
|
||||
import {z} from 'zod';
|
||||
import {CompilerErrorDetailOptions} from '../CompilerError';
|
||||
import {ExternalFunction, PartialEnvironmentConfig} from '../HIR/Environment';
|
||||
import {hasOwnProperty} from '../Utils/utils';
|
||||
import * as t from "@babel/types";
|
||||
import { z } from "zod";
|
||||
import { CompilerErrorDetailOptions } from "../CompilerError";
|
||||
import { ExternalFunction, PartialEnvironmentConfig } from "../HIR/Environment";
|
||||
import { hasOwnProperty } from "../Utils/utils";
|
||||
|
||||
const PanicThresholdOptionsSchema = z.enum([
|
||||
/*
|
||||
@@ -18,15 +18,15 @@ const PanicThresholdOptionsSchema = z.enum([
|
||||
* If Forget is invoked through `BabelPluginReactCompiler`, this will at the least
|
||||
* skip Forget compilation for the rest of current file.
|
||||
*/
|
||||
'all_errors',
|
||||
"all_errors",
|
||||
/*
|
||||
* Panic by throwing an exception only on critical or unrecognized errors.
|
||||
* For all other errors, skip the erroring function without inserting
|
||||
* a Forget-compiled version (i.e. same behavior as noEmit).
|
||||
*/
|
||||
'critical_errors',
|
||||
"critical_errors",
|
||||
// Never panic by throwing an exception.
|
||||
'none',
|
||||
"none",
|
||||
]);
|
||||
|
||||
export type PanicThresholdOptions = z.infer<typeof PanicThresholdOptionsSchema>;
|
||||
@@ -130,13 +130,11 @@ const CompilationModeSchema = z.enum([
|
||||
* false positives, since compilation has a greater impact than linting.
|
||||
* This is the default mode
|
||||
*/
|
||||
'infer',
|
||||
// Compile only components using Flow component syntax and hooks using hook syntax.
|
||||
'syntax',
|
||||
"infer",
|
||||
// Compile only functions which are explicitly annotated with "use forget"
|
||||
'annotation',
|
||||
"annotation",
|
||||
// Compile all top-level functions
|
||||
'all',
|
||||
"all",
|
||||
]);
|
||||
|
||||
export type CompilationMode = z.infer<typeof CompilationModeSchema>;
|
||||
@@ -156,27 +154,24 @@ export type CompilationMode = z.infer<typeof CompilationModeSchema>;
|
||||
*/
|
||||
export type LoggerEvent =
|
||||
| {
|
||||
kind: 'CompileError';
|
||||
kind: "CompileError";
|
||||
fnLoc: t.SourceLocation | null;
|
||||
detail: CompilerErrorDetailOptions;
|
||||
}
|
||||
| {
|
||||
kind: 'CompileDiagnostic';
|
||||
kind: "CompileDiagnostic";
|
||||
fnLoc: t.SourceLocation | null;
|
||||
detail: Omit<Omit<CompilerErrorDetailOptions, 'severity'>, 'suggestions'>;
|
||||
detail: Omit<Omit<CompilerErrorDetailOptions, "severity">, "suggestions">;
|
||||
}
|
||||
| {
|
||||
kind: 'CompileSuccess';
|
||||
kind: "CompileSuccess";
|
||||
fnLoc: t.SourceLocation | null;
|
||||
fnName: string | null;
|
||||
memoSlots: number;
|
||||
memoBlocks: number;
|
||||
memoValues: number;
|
||||
prunedMemoBlocks: number;
|
||||
prunedMemoValues: number;
|
||||
}
|
||||
| {
|
||||
kind: 'PipelineError';
|
||||
kind: "PipelineError";
|
||||
fnLoc: t.SourceLocation | null;
|
||||
data: string;
|
||||
};
|
||||
@@ -186,8 +181,8 @@ export type Logger = {
|
||||
};
|
||||
|
||||
export const defaultOptions: PluginOptions = {
|
||||
compilationMode: 'infer',
|
||||
panicThreshold: 'none',
|
||||
compilationMode: "infer",
|
||||
panicThreshold: "none",
|
||||
environment: {},
|
||||
logger: null,
|
||||
gating: null,
|
||||
@@ -196,19 +191,19 @@ export const defaultOptions: PluginOptions = {
|
||||
eslintSuppressionRules: null,
|
||||
flowSuppressions: false,
|
||||
ignoreUseNoForget: false,
|
||||
sources: filename => {
|
||||
return filename.indexOf('node_modules') === -1;
|
||||
sources: (filename) => {
|
||||
return filename.indexOf("node_modules") === -1;
|
||||
},
|
||||
enableReanimatedCheck: true,
|
||||
} as const;
|
||||
|
||||
export function parsePluginOptions(obj: unknown): PluginOptions {
|
||||
if (obj == null || typeof obj !== 'object') {
|
||||
if (obj == null || typeof obj !== "object") {
|
||||
return defaultOptions;
|
||||
}
|
||||
const parsedOptions = Object.create(null);
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === 'string') {
|
||||
if (typeof value === "string") {
|
||||
// normalize string configs to be case insensitive
|
||||
value = value.toLowerCase();
|
||||
}
|
||||
@@ -216,7 +211,7 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
|
||||
parsedOptions[key] = value;
|
||||
}
|
||||
}
|
||||
return {...defaultOptions, ...parsedOptions};
|
||||
return { ...defaultOptions, ...parsedOptions };
|
||||
}
|
||||
|
||||
function isCompilerFlag(s: string): s is keyof PluginOptions {
|
||||
|
||||
@@ -5,15 +5,14 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {Logger} from '.';
|
||||
import { NodePath } from "@babel/traverse";
|
||||
import * as t from "@babel/types";
|
||||
import prettyFormat from "pretty-format";
|
||||
import { Logger } from ".";
|
||||
import {
|
||||
HIRFunction,
|
||||
ReactiveFunction,
|
||||
assertConsistentIdentifiers,
|
||||
assertTerminalPredsExist,
|
||||
assertTerminalSuccessorsExist,
|
||||
assertValidBlockNesting,
|
||||
assertValidMutableRanges,
|
||||
@@ -22,13 +21,13 @@ import {
|
||||
mergeConsecutiveBlocks,
|
||||
mergeOverlappingReactiveScopesHIR,
|
||||
pruneUnusedLabelsHIR,
|
||||
} from '../HIR';
|
||||
} from "../HIR";
|
||||
import {
|
||||
Environment,
|
||||
EnvironmentConfig,
|
||||
ReactFunctionType,
|
||||
} from '../HIR/Environment';
|
||||
import {findContextIdentifiers} from '../HIR/FindContextIdentifiers';
|
||||
} from "../HIR/Environment";
|
||||
import { findContextIdentifiers } from "../HIR/FindContextIdentifiers";
|
||||
import {
|
||||
analyseFunctions,
|
||||
dropManualMemoization,
|
||||
@@ -36,13 +35,12 @@ import {
|
||||
inferReactivePlaces,
|
||||
inferReferenceEffects,
|
||||
inlineImmediatelyInvokedFunctionExpressions,
|
||||
} from '../Inference';
|
||||
} from "../Inference";
|
||||
import {
|
||||
constantPropagation,
|
||||
deadCodeElimination,
|
||||
pruneMaybeThrows,
|
||||
} from '../Optimization';
|
||||
import {instructionReordering} from '../Optimization/InstructionReordering';
|
||||
} from "../Optimization";
|
||||
import {
|
||||
CodegenFunction,
|
||||
alignObjectMethodScopes,
|
||||
@@ -69,23 +67,20 @@ import {
|
||||
pruneUnusedLabels,
|
||||
pruneUnusedScopes,
|
||||
renameVariables,
|
||||
} from '../ReactiveScopes';
|
||||
import {alignMethodCallScopes} from '../ReactiveScopes/AlignMethodCallScopes';
|
||||
import {alignReactiveScopesToBlockScopesHIR} from '../ReactiveScopes/AlignReactiveScopesToBlockScopesHIR';
|
||||
import {flattenReactiveLoopsHIR} from '../ReactiveScopes/FlattenReactiveLoopsHIR';
|
||||
import {flattenScopesWithHooksOrUseHIR} from '../ReactiveScopes/FlattenScopesWithHooksOrUseHIR';
|
||||
import {pruneAlwaysInvalidatingScopes} from '../ReactiveScopes/PruneAlwaysInvalidatingScopes';
|
||||
import pruneInitializationDependencies from '../ReactiveScopes/PruneInitializationDependencies';
|
||||
import {stabilizeBlockIds} from '../ReactiveScopes/StabilizeBlockIds';
|
||||
import {eliminateRedundantPhi, enterSSA, leaveSSA} from '../SSA';
|
||||
import {inferTypes} from '../TypeInference';
|
||||
} from "../ReactiveScopes";
|
||||
import { alignMethodCallScopes } from "../ReactiveScopes/AlignMethodCallScopes";
|
||||
import { alignReactiveScopesToBlockScopesHIR } from "../ReactiveScopes/AlignReactiveScopesToBlockScopesHIR";
|
||||
import { pruneAlwaysInvalidatingScopes } from "../ReactiveScopes/PruneAlwaysInvalidatingScopes";
|
||||
import { stabilizeBlockIds } from "../ReactiveScopes/StabilizeBlockIds";
|
||||
import { eliminateRedundantPhi, enterSSA, leaveSSA } from "../SSA";
|
||||
import { inferTypes } from "../TypeInference";
|
||||
import {
|
||||
logCodegenFunction,
|
||||
logDebug,
|
||||
logHIRFunction,
|
||||
logReactiveFunction,
|
||||
} from '../Utils/logger';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
} from "../Utils/logger";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import {
|
||||
validateContextVariableLValues,
|
||||
validateHooksUsage,
|
||||
@@ -95,15 +90,14 @@ import {
|
||||
validateNoSetStateInRender,
|
||||
validatePreservedManualMemoization,
|
||||
validateUseMemo,
|
||||
} from '../Validation';
|
||||
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
|
||||
import {outlineFunctions} from '../Optimization/OutlineFunctions';
|
||||
} from "../Validation";
|
||||
import pruneInitializationDependencies from "../ReactiveScopes/PruneInitializationDependencies";
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
| {kind: 'hir'; name: string; value: HIRFunction}
|
||||
| {kind: 'reactive'; name: string; value: ReactiveFunction}
|
||||
| {kind: 'debug'; name: string; value: string};
|
||||
| { kind: "ast"; name: string; value: CodegenFunction }
|
||||
| { kind: "hir"; name: string; value: HIRFunction }
|
||||
| { kind: "reactive"; name: string; value: ReactiveFunction }
|
||||
| { kind: "debug"; name: string; value: string };
|
||||
|
||||
export function* run(
|
||||
func: NodePath<
|
||||
@@ -114,22 +108,21 @@ export function* run(
|
||||
useMemoCacheIdentifier: string,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
code: string | null
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
const contextIdentifiers = findContextIdentifiers(func);
|
||||
const env = new Environment(
|
||||
func.scope,
|
||||
fnType,
|
||||
config,
|
||||
contextIdentifiers,
|
||||
logger,
|
||||
filename,
|
||||
code,
|
||||
useMemoCacheIdentifier,
|
||||
useMemoCacheIdentifier
|
||||
);
|
||||
yield {
|
||||
kind: 'debug',
|
||||
name: 'EnvironmentConfig',
|
||||
kind: "debug",
|
||||
name: "EnvironmentConfig",
|
||||
value: prettyFormat(env.config),
|
||||
};
|
||||
const ast = yield* runWithEnvironment(func, env);
|
||||
@@ -144,13 +137,13 @@ function* runWithEnvironment(
|
||||
func: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
env: Environment,
|
||||
env: Environment
|
||||
): Generator<CompilerPipelineValue, CodegenFunction> {
|
||||
const hir = lower(func, env).unwrap();
|
||||
yield log({kind: 'hir', name: 'HIR', value: hir});
|
||||
yield log({ kind: "hir", name: "HIR", value: hir });
|
||||
|
||||
pruneMaybeThrows(hir);
|
||||
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
|
||||
|
||||
validateContextVariableLValues(hir);
|
||||
validateUseMemo(hir);
|
||||
@@ -161,35 +154,35 @@ function* runWithEnvironment(
|
||||
!env.config.enableChangeDetectionForDebugging
|
||||
) {
|
||||
dropManualMemoization(hir);
|
||||
yield log({kind: 'hir', name: 'DropManualMemoization', value: hir});
|
||||
yield log({ kind: "hir", name: "DropManualMemoization", value: hir });
|
||||
}
|
||||
|
||||
inlineImmediatelyInvokedFunctionExpressions(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'InlineImmediatelyInvokedFunctionExpressions',
|
||||
kind: "hir",
|
||||
name: "InlineImmediatelyInvokedFunctionExpressions",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeConsecutiveBlocks(hir);
|
||||
yield log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
|
||||
yield log({ kind: "hir", name: "MergeConsecutiveBlocks", value: hir });
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
|
||||
enterSSA(hir);
|
||||
yield log({kind: 'hir', name: 'SSA', value: hir});
|
||||
yield log({ kind: "hir", name: "SSA", value: hir });
|
||||
|
||||
eliminateRedundantPhi(hir);
|
||||
yield log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
|
||||
yield log({ kind: "hir", name: "EliminateRedundantPhi", value: hir });
|
||||
|
||||
assertConsistentIdentifiers(hir);
|
||||
|
||||
constantPropagation(hir);
|
||||
yield log({kind: 'hir', name: 'ConstantPropagation', value: hir});
|
||||
yield log({ kind: "hir", name: "ConstantPropagation", value: hir });
|
||||
|
||||
inferTypes(hir);
|
||||
yield log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
yield log({ kind: "hir", name: "InferTypes", value: hir });
|
||||
|
||||
if (env.config.validateHooksUsage) {
|
||||
validateHooksUsage(hir);
|
||||
@@ -200,27 +193,20 @@ function* runWithEnvironment(
|
||||
}
|
||||
|
||||
analyseFunctions(hir);
|
||||
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
yield log({ kind: "hir", name: "AnalyseFunctions", value: hir });
|
||||
|
||||
inferReferenceEffects(hir);
|
||||
yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
yield log({ kind: "hir", name: "InferReferenceEffects", value: hir });
|
||||
|
||||
// Note: Has to come after infer reference effects because "dead" code may still affect inference
|
||||
deadCodeElimination(hir);
|
||||
yield log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
|
||||
if (env.config.enableInstructionReordering) {
|
||||
instructionReordering(hir);
|
||||
yield log({kind: 'hir', name: 'InstructionReordering', value: hir});
|
||||
}
|
||||
yield log({ kind: "hir", name: "DeadCodeElimination", value: hir });
|
||||
|
||||
pruneMaybeThrows(hir);
|
||||
yield log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
|
||||
yield log({ kind: "hir", name: "PruneMaybeThrows", value: hir });
|
||||
|
||||
inferMutableRanges(hir);
|
||||
yield log({kind: 'hir', name: 'InferMutableRanges', value: hir});
|
||||
yield log({ kind: "hir", name: "InferMutableRanges", value: hir });
|
||||
|
||||
if (env.config.assertValidMutableRanges) {
|
||||
assertValidMutableRanges(hir);
|
||||
@@ -235,93 +221,72 @@ function* runWithEnvironment(
|
||||
}
|
||||
|
||||
inferReactivePlaces(hir);
|
||||
yield log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
yield log({ kind: "hir", name: "InferReactivePlaces", value: hir });
|
||||
|
||||
leaveSSA(hir);
|
||||
yield log({kind: 'hir', name: 'LeaveSSA', value: hir});
|
||||
yield log({ kind: "hir", name: "LeaveSSA", value: hir });
|
||||
|
||||
inferReactiveScopeVariables(hir);
|
||||
yield log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
|
||||
|
||||
if (env.config.enableFunctionOutlining) {
|
||||
outlineFunctions(hir);
|
||||
yield log({kind: 'hir', name: 'OutlineFunctions', value: hir});
|
||||
}
|
||||
yield log({ kind: "hir", name: "InferReactiveScopeVariables", value: hir });
|
||||
|
||||
alignMethodCallScopes(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'AlignMethodCallScopes',
|
||||
kind: "hir",
|
||||
name: "AlignMethodCallScopes",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignObjectMethodScopes(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'AlignObjectMethodScopes',
|
||||
kind: "hir",
|
||||
name: "AlignObjectMethodScopes",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
const fbtOperands = memoizeFbtOperandsInSameScope(hir);
|
||||
memoizeFbtOperandsInSameScope(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'MemoizeFbtAndMacroOperandsInSameScope',
|
||||
kind: "hir",
|
||||
name: "MemoizeFbtOperandsInSameScope",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
if (env.config.enableReactiveScopesInHIR) {
|
||||
pruneUnusedLabelsHIR(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'PruneUnusedLabelsHIR',
|
||||
kind: "hir",
|
||||
name: "PruneUnusedLabelsHIR",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
alignReactiveScopesToBlockScopesHIR(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'AlignReactiveScopesToBlockScopesHIR',
|
||||
kind: "hir",
|
||||
name: "AlignReactiveScopesToBlockScopesHIR",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
mergeOverlappingReactiveScopesHIR(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'MergeOverlappingReactiveScopesHIR',
|
||||
kind: "hir",
|
||||
name: "MergeOverlappingReactiveScopesHIR",
|
||||
value: hir,
|
||||
});
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
buildReactiveScopeTerminalsHIR(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'BuildReactiveScopeTerminalsHIR',
|
||||
kind: "hir",
|
||||
name: "BuildReactiveScopeTerminalsHIR",
|
||||
value: hir,
|
||||
});
|
||||
|
||||
assertValidBlockNesting(hir);
|
||||
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenReactiveLoopsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
yield log({
|
||||
kind: 'hir',
|
||||
name: 'FlattenScopesWithHooksOrUseHIR',
|
||||
value: hir,
|
||||
});
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
assertTerminalPredsExist(hir);
|
||||
}
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveFunction',
|
||||
kind: "reactive",
|
||||
name: "BuildReactiveFunction",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
@@ -329,147 +294,147 @@ function* runWithEnvironment(
|
||||
|
||||
pruneUnusedLabels(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLabels',
|
||||
kind: "reactive",
|
||||
name: "PruneUnusedLabels",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
if (!env.config.enableReactiveScopesInHIR) {
|
||||
alignReactiveScopesToBlockScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'AlignReactiveScopesToBlockScopes',
|
||||
kind: "reactive",
|
||||
name: "AlignReactiveScopesToBlockScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
mergeOverlappingReactiveScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'MergeOverlappingReactiveScopes',
|
||||
kind: "reactive",
|
||||
name: "MergeOverlappingReactiveScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
buildReactiveBlocks(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveBlocks',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
flattenReactiveLoops(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'FlattenReactiveLoops',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
flattenScopesWithHooksOrUse(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'FlattenScopesWithHooks',
|
||||
kind: "reactive",
|
||||
name: "BuildReactiveBlocks",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
}
|
||||
|
||||
flattenReactiveLoops(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "FlattenReactiveLoops",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
assertScopeInstructionsWithinScopes(reactiveFunction);
|
||||
|
||||
flattenScopesWithHooksOrUse(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "FlattenScopesWithHooks",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
propagateScopeDependencies(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PropagateScopeDependencies',
|
||||
kind: "reactive",
|
||||
name: "PropagateScopeDependencies",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneNonEscapingScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonEscapingScopes',
|
||||
kind: "reactive",
|
||||
name: "PruneNonEscapingScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneNonReactiveDependencies(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneNonReactiveDependencies',
|
||||
kind: "reactive",
|
||||
name: "PruneNonReactiveDependencies",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedScopes',
|
||||
kind: "reactive",
|
||||
name: "PruneUnusedScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'MergeReactiveScopesThatInvalidateTogether',
|
||||
kind: "reactive",
|
||||
name: "MergeReactiveScopesThatInvalidateTogether",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneAlwaysInvalidatingScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneAlwaysInvalidatingScopes',
|
||||
kind: "reactive",
|
||||
name: "PruneAlwaysInvalidatingScopes",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
if (env.config.enableChangeDetectionForDebugging != null) {
|
||||
pruneInitializationDependencies(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneInitializationDependencies',
|
||||
kind: "reactive",
|
||||
name: "PruneInitializationDependencies",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
}
|
||||
|
||||
propagateEarlyReturns(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PropagateEarlyReturns',
|
||||
kind: "reactive",
|
||||
name: "PropagateEarlyReturns",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
promoteUsedTemporaries(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PromoteUsedTemporaries',
|
||||
kind: "reactive",
|
||||
name: "PromoteUsedTemporaries",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneUnusedLValues(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneUnusedLValues',
|
||||
kind: "reactive",
|
||||
name: "PruneUnusedLValues",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
extractScopeDeclarationsFromDestructuring(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'ExtractScopeDeclarationsFromDestructuring',
|
||||
kind: "reactive",
|
||||
name: "ExtractScopeDeclarationsFromDestructuring",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
stabilizeBlockIds(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'StabilizeBlockIds',
|
||||
kind: "reactive",
|
||||
name: "StabilizeBlockIds",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
const uniqueIdentifiers = renameVariables(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'RenameVariables',
|
||||
kind: "reactive",
|
||||
name: "RenameVariables",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
pruneHoistedContexts(reactiveFunction);
|
||||
yield log({
|
||||
kind: 'reactive',
|
||||
name: 'PruneHoistedContexts',
|
||||
kind: "reactive",
|
||||
name: "PruneHoistedContexts",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
@@ -484,14 +449,8 @@ function* runWithEnvironment(
|
||||
validatePreservedManualMemoization(reactiveFunction);
|
||||
}
|
||||
|
||||
const ast = codegenFunction(reactiveFunction, {
|
||||
uniqueIdentifiers,
|
||||
fbtOperands,
|
||||
}).unwrap();
|
||||
yield log({kind: 'ast', name: 'Codegen', value: ast});
|
||||
for (const outlined of ast.outlined) {
|
||||
yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
|
||||
}
|
||||
const ast = codegenFunction(reactiveFunction, uniqueIdentifiers).unwrap();
|
||||
yield log({ kind: "ast", name: "Codegen", value: ast });
|
||||
|
||||
/**
|
||||
* This flag should be only set for unit / fixture tests to check
|
||||
@@ -499,7 +458,7 @@ function* runWithEnvironment(
|
||||
* thrown by babel functions or other unexpected exceptions).
|
||||
*/
|
||||
if (env.config.throwUnknownException__testonly) {
|
||||
throw new Error('unexpected error');
|
||||
throw new Error("unexpected error");
|
||||
}
|
||||
|
||||
return ast;
|
||||
@@ -514,7 +473,7 @@ export function compileFn(
|
||||
useMemoCacheIdentifier: string,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
code: string | null
|
||||
): CodegenFunction {
|
||||
let generator = run(
|
||||
func,
|
||||
@@ -523,7 +482,7 @@ export function compileFn(
|
||||
useMemoCacheIdentifier,
|
||||
logger,
|
||||
filename,
|
||||
code,
|
||||
code
|
||||
);
|
||||
while (true) {
|
||||
const next = generator.next();
|
||||
@@ -535,24 +494,24 @@ export function compileFn(
|
||||
|
||||
export function log(value: CompilerPipelineValue): CompilerPipelineValue {
|
||||
switch (value.kind) {
|
||||
case 'ast': {
|
||||
case "ast": {
|
||||
logCodegenFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'hir': {
|
||||
case "hir": {
|
||||
logHIRFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'reactive': {
|
||||
case "reactive": {
|
||||
logReactiveFunction(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
case 'debug': {
|
||||
case "debug": {
|
||||
logDebug(value.name, value.value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(value, 'Unexpected compilation kind');
|
||||
assertExhaustive(value, "Unexpected compilation kind");
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import {hasOwnProperty} from '../Utils/utils';
|
||||
import {PluginOptions} from './Options';
|
||||
import type * as BabelCore from "@babel/core";
|
||||
import { hasOwnProperty } from "../Utils/utils";
|
||||
import { PluginOptions } from "./Options";
|
||||
|
||||
function hasModule(name: string): boolean {
|
||||
try {
|
||||
return !!require.resolve(name);
|
||||
} catch (error: any) {
|
||||
if (
|
||||
error.code === 'MODULE_NOT_FOUND' &&
|
||||
error.code === "MODULE_NOT_FOUND" &&
|
||||
error.message.indexOf(name) !== -1
|
||||
) {
|
||||
return false;
|
||||
@@ -24,22 +24,22 @@ function hasModule(name: string): boolean {
|
||||
* See https://github.com/expo/expo/blob/e4b8d86442482c7316365a6b7ec1141eec73409d/packages/babel-preset-expo/src/index.ts#L300-L301
|
||||
*/
|
||||
export function pipelineUsesReanimatedPlugin(
|
||||
plugins: Array<BabelCore.PluginItem> | null | undefined,
|
||||
plugins: Array<BabelCore.PluginItem> | null | undefined
|
||||
): boolean {
|
||||
if (Array.isArray(plugins)) {
|
||||
for (const plugin of plugins) {
|
||||
if (hasOwnProperty(plugin, 'key')) {
|
||||
if (hasOwnProperty(plugin, "key")) {
|
||||
const key = (plugin as any).key; // already checked
|
||||
if (
|
||||
typeof key === 'string' &&
|
||||
key.indexOf('react-native-reanimated') !== -1
|
||||
typeof key === "string" &&
|
||||
key.indexOf("react-native-reanimated") !== -1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasModule('react-native-reanimated');
|
||||
return hasModule("react-native-reanimated");
|
||||
}
|
||||
|
||||
export function injectReanimatedFlag(options: PluginOptions): PluginOptions {
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {NodePath} from '@babel/core';
|
||||
import * as t from '@babel/types';
|
||||
import { NodePath } from "@babel/core";
|
||||
import * as t from "@babel/types";
|
||||
import {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
CompilerSuggestionOperation,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
} from "../CompilerError";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
|
||||
/**
|
||||
* Captures the start and end range of a pair of eslint-disable ... eslint-enable comments. In the
|
||||
@@ -29,7 +29,7 @@ export type SuppressionRange = {
|
||||
source: SuppressionSource;
|
||||
};
|
||||
|
||||
type SuppressionSource = 'Eslint' | 'Flow';
|
||||
type SuppressionSource = "Eslint" | "Flow";
|
||||
|
||||
/**
|
||||
* An suppression affects a function if:
|
||||
@@ -38,7 +38,7 @@ type SuppressionSource = 'Eslint' | 'Flow';
|
||||
*/
|
||||
export function filterSuppressionsThatAffectFunction(
|
||||
suppressionRanges: Array<SuppressionRange>,
|
||||
fn: NodePath<t.Function>,
|
||||
fn: NodePath<t.Function>
|
||||
): Array<SuppressionRange> {
|
||||
const suppressionsInScope: Array<SuppressionRange> = [];
|
||||
const fnNode = fn.node;
|
||||
@@ -78,21 +78,21 @@ export function filterSuppressionsThatAffectFunction(
|
||||
export function findProgramSuppressions(
|
||||
programComments: Array<t.Comment>,
|
||||
ruleNames: Array<string>,
|
||||
flowSuppressions: boolean,
|
||||
flowSuppressions: boolean
|
||||
): Array<SuppressionRange> {
|
||||
const suppressionRanges: Array<SuppressionRange> = [];
|
||||
let disableComment: t.Comment | null = null;
|
||||
let enableComment: t.Comment | null = null;
|
||||
let source: SuppressionSource | null = null;
|
||||
|
||||
const rulePattern = `(${ruleNames.join('|')})`;
|
||||
const rulePattern = `(${ruleNames.join("|")})`;
|
||||
const disableNextLinePattern = new RegExp(
|
||||
`eslint-disable-next-line ${rulePattern}`,
|
||||
`eslint-disable-next-line ${rulePattern}`
|
||||
);
|
||||
const disablePattern = new RegExp(`eslint-disable ${rulePattern}`);
|
||||
const enablePattern = new RegExp(`eslint-enable ${rulePattern}`);
|
||||
const flowSuppressionPattern = new RegExp(
|
||||
'\\$(FlowFixMe\\w*|FlowExpectedError|FlowIssue)\\[react\\-rule',
|
||||
"\\$(FlowFixMe\\w*|FlowExpectedError|FlowIssue)\\[react\\-rule"
|
||||
);
|
||||
|
||||
for (const comment of programComments) {
|
||||
@@ -110,7 +110,7 @@ export function findProgramSuppressions(
|
||||
) {
|
||||
disableComment = comment;
|
||||
enableComment = comment;
|
||||
source = 'Eslint';
|
||||
source = "Eslint";
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -120,15 +120,15 @@ export function findProgramSuppressions(
|
||||
) {
|
||||
disableComment = comment;
|
||||
enableComment = comment;
|
||||
source = 'Flow';
|
||||
source = "Flow";
|
||||
}
|
||||
|
||||
if (disablePattern.test(comment.value)) {
|
||||
disableComment = comment;
|
||||
source = 'Eslint';
|
||||
source = "Eslint";
|
||||
}
|
||||
|
||||
if (enablePattern.test(comment.value) && source === 'Eslint') {
|
||||
if (enablePattern.test(comment.value) && source === "Eslint") {
|
||||
enableComment = comment;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ export function findProgramSuppressions(
|
||||
}
|
||||
|
||||
export function suppressionsToCompilerError(
|
||||
suppressionRanges: Array<SuppressionRange>,
|
||||
suppressionRanges: Array<SuppressionRange>
|
||||
): CompilerError | null {
|
||||
if (suppressionRanges.length === 0) {
|
||||
return null;
|
||||
@@ -162,21 +162,21 @@ export function suppressionsToCompilerError(
|
||||
}
|
||||
let reason, suggestion;
|
||||
switch (suppressionRange.source) {
|
||||
case 'Eslint':
|
||||
case "Eslint":
|
||||
reason =
|
||||
'React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled';
|
||||
"React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled";
|
||||
suggestion =
|
||||
'Remove the ESLint suppression and address the React error';
|
||||
"Remove the ESLint suppression and address the React error";
|
||||
break;
|
||||
case 'Flow':
|
||||
case "Flow":
|
||||
reason =
|
||||
'React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow';
|
||||
suggestion = 'Remove the Flow suppression and address the React error';
|
||||
"React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow";
|
||||
suggestion = "Remove the Flow suppression and address the React error";
|
||||
break;
|
||||
default:
|
||||
assertExhaustive(
|
||||
suppressionRange.source,
|
||||
'Unhandled suppression source',
|
||||
"Unhandled suppression source"
|
||||
);
|
||||
}
|
||||
error.pushErrorDetail(
|
||||
@@ -195,7 +195,7 @@ export function suppressionsToCompilerError(
|
||||
op: CompilerSuggestionOperation.Remove,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
return error;
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './Gating';
|
||||
export * from './Imports';
|
||||
export * from './Options';
|
||||
export * from './Pipeline';
|
||||
export * from './Program';
|
||||
export * from './Suppression';
|
||||
export * from "./Gating";
|
||||
export * from "./Imports";
|
||||
export * from "./Options";
|
||||
export * from "./Pipeline";
|
||||
export * from "./Program";
|
||||
export * from "./Suppression";
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
SourceLocation,
|
||||
} from './HIR';
|
||||
import {printPlace} from './PrintHIR';
|
||||
} from "./HIR";
|
||||
import { printPlace } from "./PrintHIR";
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from './visitors';
|
||||
} from "./visitors";
|
||||
|
||||
/*
|
||||
* Validation pass to check that there is a 1:1 mapping between Identifier objects and IdentifierIds,
|
||||
@@ -44,7 +44,7 @@ export function assertConsistentIdentifiers(fn: HIRFunction): void {
|
||||
CompilerError.invariant(!assignments.has(instr.lvalue.identifier.id), {
|
||||
reason: `Expected lvalues to be assigned exactly once`,
|
||||
description: `Found duplicate assignment of '${printPlace(
|
||||
instr.lvalue,
|
||||
instr.lvalue
|
||||
)}'`,
|
||||
loc: instr.lvalue.loc,
|
||||
suggestions: null,
|
||||
@@ -68,7 +68,7 @@ type Identifiers = Map<IdentifierId, Identifier>;
|
||||
function validate(
|
||||
identifiers: Identifiers,
|
||||
identifier: Identifier,
|
||||
loc: SourceLocation | null = null,
|
||||
loc: SourceLocation | null = null
|
||||
): void {
|
||||
const previous = identifiers.get(identifier.id);
|
||||
if (previous === undefined) {
|
||||
|
||||
@@ -1,48 +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} from '../CompilerError';
|
||||
import {GeneratedSource, HIRFunction} from './HIR';
|
||||
import {printTerminal} from './PrintHIR';
|
||||
import {eachTerminalSuccessor, mapTerminalSuccessors} from './visitors';
|
||||
|
||||
export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
mapTerminalSuccessors(block.terminal, successor => {
|
||||
CompilerError.invariant(fn.body.blocks.has(successor), {
|
||||
reason: `Terminal successor references unknown block`,
|
||||
description: `Block bb${successor} does not exist for terminal '${printTerminal(
|
||||
block.terminal,
|
||||
)}'`,
|
||||
loc: (block.terminal as any).loc ?? GeneratedSource,
|
||||
suggestions: null,
|
||||
});
|
||||
return successor;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function assertTerminalPredsExist(fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const pred of block.preds) {
|
||||
const predBlock = fn.body.blocks.get(pred);
|
||||
CompilerError.invariant(predBlock != null, {
|
||||
reason: 'Expected predecessor block to exist',
|
||||
description: `Block ${block.id} references non-existent ${pred}`,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
CompilerError.invariant(
|
||||
[...eachTerminalSuccessor(predBlock.terminal)].includes(block.id),
|
||||
{
|
||||
reason: 'Terminal successor does not reference correct predecessor',
|
||||
description: `Block bb${block.id} has bb${predBlock.id} as a predecessor, but bb${predBlock.id}'s successors do not include bb${block.id}`,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { GeneratedSource, HIRFunction } from "./HIR";
|
||||
import { printTerminal } from "./PrintHIR";
|
||||
import { mapTerminalSuccessors } from "./visitors";
|
||||
|
||||
export function assertTerminalSuccessorsExist(fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
mapTerminalSuccessors(block.terminal, (successor) => {
|
||||
CompilerError.invariant(fn.body.blocks.has(successor), {
|
||||
reason: `Terminal successor references unknown block`,
|
||||
description: `Block bb${successor} does not exist for terminal '${printTerminal(
|
||||
block.terminal
|
||||
)}'`,
|
||||
loc: (block.terminal as any).loc ?? GeneratedSource,
|
||||
suggestions: null,
|
||||
});
|
||||
return successor;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {CompilerError} from '..';
|
||||
import { CompilerError } from "..";
|
||||
import {
|
||||
BlockId,
|
||||
GeneratedSource,
|
||||
@@ -7,13 +7,13 @@ import {
|
||||
Place,
|
||||
ReactiveScope,
|
||||
ScopeId,
|
||||
} from './HIR';
|
||||
} from "./HIR";
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionOperand,
|
||||
eachTerminalOperand,
|
||||
terminalFallthrough,
|
||||
} from './visitors';
|
||||
} from "./visitors";
|
||||
|
||||
/**
|
||||
* This pass asserts that program blocks and scopes properly form a tree hierarchy
|
||||
@@ -41,11 +41,11 @@ import {
|
||||
*/
|
||||
type Block =
|
||||
| ({
|
||||
kind: 'ProgramBlockSubtree';
|
||||
kind: "ProgramBlockSubtree";
|
||||
id: BlockId;
|
||||
} & MutableRange)
|
||||
| ({
|
||||
kind: 'Scope';
|
||||
kind: "Scope";
|
||||
id: ScopeId;
|
||||
} & MutableRange);
|
||||
|
||||
@@ -96,7 +96,7 @@ export function getScopes(fn: HIRFunction): Set<ReactiveScope> {
|
||||
*/
|
||||
export function rangePreOrderComparator(
|
||||
a: MutableRange,
|
||||
b: MutableRange,
|
||||
b: MutableRange
|
||||
): number {
|
||||
const startDiff = a.start - b.start;
|
||||
if (startDiff !== 0) return startDiff;
|
||||
@@ -108,7 +108,7 @@ export function recursivelyTraverseItems<T, TContext>(
|
||||
getRange: (val: T) => MutableRange,
|
||||
context: TContext,
|
||||
enter: (val: T, context: TContext) => void,
|
||||
exit: (val: T, context: TContext) => void,
|
||||
exit: (val: T, context: TContext) => void
|
||||
): void {
|
||||
items.sort((a, b) => rangePreOrderComparator(getRange(a), getRange(b)));
|
||||
let activeItems: Array<T> = [];
|
||||
@@ -122,7 +122,7 @@ export function recursivelyTraverseItems<T, TContext>(
|
||||
const disjoint = currRange.start >= maybeParentRange.end;
|
||||
const nested = currRange.end <= maybeParentRange.end;
|
||||
CompilerError.invariant(disjoint || nested, {
|
||||
reason: 'Invalid nesting in program blocks or scopes',
|
||||
reason: "Invalid nesting in program blocks or scopes",
|
||||
description: `Items overlap but are not nested: ${maybeParentRange.start}:${maybeParentRange.end}(${currRange.start}:${currRange.end})`,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
@@ -148,8 +148,8 @@ const no_op: () => void = () => {};
|
||||
export function assertValidBlockNesting(fn: HIRFunction): void {
|
||||
const scopes = getScopes(fn);
|
||||
|
||||
const blocks: Array<Block> = [...scopes].map(scope => ({
|
||||
kind: 'Scope',
|
||||
const blocks: Array<Block> = [...scopes].map((scope) => ({
|
||||
kind: "Scope",
|
||||
id: scope.id,
|
||||
...scope.range,
|
||||
})) as Array<Block>;
|
||||
@@ -159,7 +159,7 @@ export function assertValidBlockNesting(fn: HIRFunction): void {
|
||||
const fallthrough = fn.body.blocks.get(fallthroughId)!;
|
||||
const end = fallthrough.instructions[0]?.id ?? fallthrough.terminal.id;
|
||||
blocks.push({
|
||||
kind: 'ProgramBlockSubtree',
|
||||
kind: "ProgramBlockSubtree",
|
||||
id: block.id,
|
||||
start: block.terminal.id,
|
||||
end,
|
||||
@@ -167,5 +167,5 @@ export function assertValidBlockNesting(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
|
||||
recursivelyTraverseItems(blocks, block => block, null, no_op, no_op);
|
||||
recursivelyTraverseItems(blocks, (block) => block, null, no_op, no_op);
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import invariant from 'invariant';
|
||||
import {HIRFunction, Identifier, MutableRange} from './HIR';
|
||||
import invariant from "invariant";
|
||||
import { HIRFunction, Identifier, MutableRange } from "./HIR";
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionOperand,
|
||||
eachTerminalOperand,
|
||||
} from './visitors';
|
||||
} from "./visitors";
|
||||
|
||||
/*
|
||||
* Checks that all mutable ranges in the function are well-formed, with
|
||||
@@ -49,8 +49,8 @@ function validateMutableRange(mutableRange: MutableRange): void {
|
||||
invariant(
|
||||
(mutableRange.start === 0 && mutableRange.end === 0) ||
|
||||
mutableRange.end > mutableRange.start,
|
||||
'Identifier scope mutableRange was invalid: [%s:%s]',
|
||||
"Identifier scope mutableRange was invalid: [%s:%s]",
|
||||
mutableRange.start,
|
||||
mutableRange.end,
|
||||
mutableRange.end
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {getScopes, recursivelyTraverseItems} from './AssertValidBlockNesting';
|
||||
import {Environment} from './Environment';
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { getScopes, recursivelyTraverseItems } from "./AssertValidBlockNesting";
|
||||
import { Environment } from "./Environment";
|
||||
import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
@@ -12,12 +12,7 @@ import {
|
||||
ReactiveScope,
|
||||
ReactiveScopeTerminal,
|
||||
ScopeId,
|
||||
} from './HIR';
|
||||
import {
|
||||
markInstructionIds,
|
||||
markPredecessors,
|
||||
reversePostorderBlocks,
|
||||
} from './HIRBuilder';
|
||||
} from "./HIR";
|
||||
|
||||
/**
|
||||
* This pass assumes that all program blocks are properly nested with respect to fallthroughs
|
||||
@@ -77,14 +72,14 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
|
||||
const queuedRewrites: Array<TerminalRewriteInfo> = [];
|
||||
recursivelyTraverseItems(
|
||||
[...getScopes(fn)],
|
||||
scope => scope.range,
|
||||
(scope) => scope.range,
|
||||
{
|
||||
fallthroughs: new Map(),
|
||||
rewrites: queuedRewrites,
|
||||
env: fn.env,
|
||||
},
|
||||
pushStartScopeTerminal,
|
||||
pushEndScopeTerminal,
|
||||
pushEndScopeTerminal
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -147,9 +142,16 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
|
||||
|
||||
/**
|
||||
* Step 3:
|
||||
* Repoint phis when they refer to a rewritten block.
|
||||
* Repoint preds and phis when they refer to a rewritten block.
|
||||
*/
|
||||
for (const [, block] of originalBlocks) {
|
||||
for (const pred of block.preds) {
|
||||
const newId = rewrittenFinalBlocks.get(pred);
|
||||
if (newId != null) {
|
||||
block.preds.delete(pred);
|
||||
block.preds.add(newId);
|
||||
}
|
||||
}
|
||||
for (const phi of block.phis) {
|
||||
for (const [originalId, value] of phi.operands) {
|
||||
const newId = rewrittenFinalBlocks.get(originalId);
|
||||
@@ -160,48 +162,18 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4:
|
||||
* Fixup the HIR to restore RPO, ensure correct predecessors, and
|
||||
* renumber instructions. Note that the renumbering instructions
|
||||
* invalidates scope and identifier ranges, so we fix them in the
|
||||
* next step.
|
||||
*/
|
||||
reversePostorderBlocks(fn.body);
|
||||
markPredecessors(fn.body);
|
||||
markInstructionIds(fn.body);
|
||||
|
||||
/**
|
||||
* Step 5:
|
||||
* Fix scope and identifier ranges to account for renumbered instructions
|
||||
*/
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
const terminal = block.terminal;
|
||||
if (terminal.kind === 'scope' || terminal.kind === 'pruned-scope') {
|
||||
/*
|
||||
* Scope ranges should always align to start at the 'scope' terminal
|
||||
* and end at the first instruction of the fallthrough block
|
||||
*/
|
||||
const fallthroughBlock = fn.body.blocks.get(terminal.fallthrough)!;
|
||||
const firstId =
|
||||
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
|
||||
terminal.scope.range.start = terminal.id;
|
||||
terminal.scope.range.end = firstId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TerminalRewriteInfo =
|
||||
| {
|
||||
kind: 'StartScope';
|
||||
kind: "StartScope";
|
||||
blockId: BlockId;
|
||||
fallthroughId: BlockId;
|
||||
instrId: InstructionId;
|
||||
scope: ReactiveScope;
|
||||
}
|
||||
| {
|
||||
kind: 'EndScope';
|
||||
kind: "EndScope";
|
||||
instrId: InstructionId;
|
||||
fallthroughId: BlockId;
|
||||
};
|
||||
@@ -218,12 +190,12 @@ type ScopeTraversalContext = {
|
||||
|
||||
function pushStartScopeTerminal(
|
||||
scope: ReactiveScope,
|
||||
context: ScopeTraversalContext,
|
||||
context: ScopeTraversalContext
|
||||
): void {
|
||||
const blockId = context.env.nextBlockId;
|
||||
const fallthroughId = context.env.nextBlockId;
|
||||
context.rewrites.push({
|
||||
kind: 'StartScope',
|
||||
kind: "StartScope",
|
||||
blockId,
|
||||
fallthroughId,
|
||||
instrId: scope.range.start,
|
||||
@@ -234,15 +206,15 @@ function pushStartScopeTerminal(
|
||||
|
||||
function pushEndScopeTerminal(
|
||||
scope: ReactiveScope,
|
||||
context: ScopeTraversalContext,
|
||||
context: ScopeTraversalContext
|
||||
): void {
|
||||
const fallthroughId = context.fallthroughs.get(scope.id);
|
||||
CompilerError.invariant(fallthroughId != null, {
|
||||
reason: 'Expected scope to exist',
|
||||
reason: "Expected scope to exist",
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
context.rewrites.push({
|
||||
kind: 'EndScope',
|
||||
kind: "EndScope",
|
||||
fallthroughId,
|
||||
instrId: scope.range.end,
|
||||
});
|
||||
@@ -276,13 +248,13 @@ type RewriteContext = {
|
||||
function handleRewrite(
|
||||
terminalInfo: TerminalRewriteInfo,
|
||||
idx: number,
|
||||
context: RewriteContext,
|
||||
context: RewriteContext
|
||||
): void {
|
||||
// TODO make consistent instruction IDs instead of reusing
|
||||
const terminal: ReactiveScopeTerminal | GotoTerminal =
|
||||
terminalInfo.kind === 'StartScope'
|
||||
terminalInfo.kind === "StartScope"
|
||||
? {
|
||||
kind: 'scope',
|
||||
kind: "scope",
|
||||
fallthrough: terminalInfo.fallthroughId,
|
||||
block: terminalInfo.blockId,
|
||||
scope: terminalInfo.scope,
|
||||
@@ -290,7 +262,7 @@ function handleRewrite(
|
||||
loc: GeneratedSource,
|
||||
}
|
||||
: {
|
||||
kind: 'goto',
|
||||
kind: "goto",
|
||||
variant: GotoVariant.Break,
|
||||
block: terminalInfo.fallthroughId,
|
||||
id: terminalInfo.instrId,
|
||||
@@ -309,7 +281,7 @@ function handleRewrite(
|
||||
});
|
||||
context.nextPreds = new Set([currBlockId]);
|
||||
context.nextBlockId =
|
||||
terminalInfo.kind === 'StartScope'
|
||||
terminalInfo.kind === "StartScope"
|
||||
? terminalInfo.blockId
|
||||
: terminalInfo.fallthroughId;
|
||||
context.instrSliceIdx = idx;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {BlockId, HIRFunction, computePostDominatorTree} from '.';
|
||||
import {CompilerError} from '..';
|
||||
import { BlockId, HIRFunction, computePostDominatorTree } from ".";
|
||||
import { CompilerError } from "..";
|
||||
|
||||
export function computeUnconditionalBlocks(fn: HIRFunction): Set<BlockId> {
|
||||
// Construct the set of blocks that is always reachable from the entry block.
|
||||
@@ -23,7 +23,7 @@ export function computeUnconditionalBlocks(fn: HIRFunction): Set<BlockId> {
|
||||
while (current !== null && current !== exit) {
|
||||
CompilerError.invariant(!unconditionalBlocks.has(current), {
|
||||
reason:
|
||||
'Internal error: non-terminating loop in ComputeUnconditionalBlocks',
|
||||
"Internal error: non-terminating loop in ComputeUnconditionalBlocks",
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {BlockId, HIRFunction} from './HIR';
|
||||
import {eachTerminalSuccessor} from './visitors';
|
||||
import prettyFormat from "pretty-format";
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { BlockId, HIRFunction } from "./HIR";
|
||||
import { eachTerminalSuccessor } from "./visitors";
|
||||
|
||||
/*
|
||||
* Computes the dominator tree of the given function. The returned `Dominator` stores the immediate
|
||||
@@ -34,7 +34,7 @@ export function computeDominatorTree(fn: HIRFunction): Dominator<BlockId> {
|
||||
*/
|
||||
export function computePostDominatorTree(
|
||||
fn: HIRFunction,
|
||||
options: {includeThrowsAsExitNode: boolean},
|
||||
options: { includeThrowsAsExitNode: boolean }
|
||||
): PostDominator<BlockId> {
|
||||
const graph = buildReverseGraph(fn, options.includeThrowsAsExitNode);
|
||||
const nodes = computeImmediateDominators(graph);
|
||||
@@ -87,7 +87,7 @@ export class Dominator<T> {
|
||||
get(id: T): T | null {
|
||||
const dominator = this.#nodes.get(id);
|
||||
CompilerError.invariant(dominator !== undefined, {
|
||||
reason: 'Unknown node',
|
||||
reason: "Unknown node",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -128,7 +128,7 @@ export class PostDominator<T> {
|
||||
get(id: T): T | null {
|
||||
const dominator = this.#nodes.get(id);
|
||||
CompilerError.invariant(dominator !== undefined, {
|
||||
reason: 'Unknown node',
|
||||
reason: "Unknown node",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -217,7 +217,7 @@ function intersect<T>(a: T, b: T, graph: Graph<T>, nodes: Map<T, T>): T {
|
||||
|
||||
// Turns the HIRFunction into a simplified internal form that is shared for dominator/post-dominator computation
|
||||
function buildGraph(fn: HIRFunction): Graph<BlockId> {
|
||||
const graph: Graph<BlockId> = {entry: fn.body.entry, nodes: new Map()};
|
||||
const graph: Graph<BlockId> = { entry: fn.body.entry, nodes: new Map() };
|
||||
let index = 0;
|
||||
for (const [id, block] of fn.body.blocks) {
|
||||
graph.nodes.set(id, {
|
||||
@@ -237,7 +237,7 @@ function buildGraph(fn: HIRFunction): Graph<BlockId> {
|
||||
*/
|
||||
function buildReverseGraph(
|
||||
fn: HIRFunction,
|
||||
includeThrowsAsExitNode: boolean,
|
||||
includeThrowsAsExitNode: boolean
|
||||
): Graph<BlockId> {
|
||||
const nodes: Map<BlockId, Node<BlockId>> = new Map();
|
||||
const exitId = fn.env.nextBlockId;
|
||||
@@ -256,10 +256,10 @@ function buildReverseGraph(
|
||||
preds: new Set(eachTerminalSuccessor(block.terminal)),
|
||||
succs: new Set(block.preds),
|
||||
};
|
||||
if (block.terminal.kind === 'return') {
|
||||
if (block.terminal.kind === "return") {
|
||||
node.preds.add(exitId);
|
||||
exit.succs.add(id);
|
||||
} else if (block.terminal.kind === 'throw' && includeThrowsAsExitNode) {
|
||||
} else if (block.terminal.kind === "throw" && includeThrowsAsExitNode) {
|
||||
node.preds.add(exitId);
|
||||
exit.succs.add(id);
|
||||
}
|
||||
@@ -282,7 +282,7 @@ function buildReverseGraph(
|
||||
}
|
||||
visit(exitId);
|
||||
|
||||
const rpo: Graph<BlockId> = {entry: exitId, nodes: new Map()};
|
||||
const rpo: Graph<BlockId> = { entry: exitId, nodes: new Map() };
|
||||
let index = 0;
|
||||
for (const id of postorder.reverse()) {
|
||||
const node = nodes.get(id)!;
|
||||
|
||||
@@ -5,37 +5,34 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as t from '@babel/types';
|
||||
import {ZodError, z} from 'zod';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Logger} from '../Entrypoint';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import * as t from "@babel/types";
|
||||
import { ZodError, z } from "zod";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { Logger } from "../Entrypoint";
|
||||
import { Err, Ok, Result } from "../Utils/Result";
|
||||
import {
|
||||
DEFAULT_GLOBALS,
|
||||
DEFAULT_SHAPES,
|
||||
Global,
|
||||
GlobalRegistry,
|
||||
installReAnimatedTypes,
|
||||
} from './Globals';
|
||||
} from "./Globals";
|
||||
import {
|
||||
BlockId,
|
||||
BuiltInType,
|
||||
Effect,
|
||||
FunctionType,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
NonLocalBinding,
|
||||
PolyType,
|
||||
ScopeId,
|
||||
Type,
|
||||
ValidatedIdentifier,
|
||||
ValueKind,
|
||||
makeBlockId,
|
||||
makeIdentifierId,
|
||||
makeIdentifierName,
|
||||
makeScopeId,
|
||||
} from './HIR';
|
||||
} from "./HIR";
|
||||
import {
|
||||
BuiltInMixedReadonlyId,
|
||||
DefaultMutatingHook,
|
||||
@@ -43,8 +40,7 @@ import {
|
||||
FunctionSignature,
|
||||
ShapeRegistry,
|
||||
addHook,
|
||||
} from './ObjectShape';
|
||||
import {Scope as BabelScope} from '@babel/traverse';
|
||||
} from "./ObjectShape";
|
||||
|
||||
export const ExternalFunctionSchema = z.object({
|
||||
// Source for the imported module that exports the `importSpecifierName` functions
|
||||
@@ -61,8 +57,8 @@ export const InstrumentationSchema = z
|
||||
globalGating: z.string().nullish(),
|
||||
})
|
||||
.refine(
|
||||
opts => opts.gating != null || opts.globalGating != null,
|
||||
'Expected at least one of gating or globalGating',
|
||||
(opts) => opts.gating != null || opts.globalGating != null,
|
||||
"Expected at least one of gating or globalGating"
|
||||
);
|
||||
|
||||
export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
|
||||
@@ -123,18 +119,6 @@ export type Hook = z.infer<typeof HookSchema>;
|
||||
const EnvironmentConfigSchema = z.object({
|
||||
customHooks: z.map(z.string(), HookSchema).optional().default(new Map()),
|
||||
|
||||
/**
|
||||
* A list of functions which the application compiles as macros, where
|
||||
* the compiler must ensure they are not compiled to rename the macro or separate the
|
||||
* "function" from its argument.
|
||||
*
|
||||
* For example, Meta has some APIs such as `featureflag("name-of-feature-flag")` which
|
||||
* are rewritten by a plugin. Assigning `featureflag` to a temporary would break the
|
||||
* plugin since it looks specifically for the name of the function being invoked, not
|
||||
* following aliases.
|
||||
*/
|
||||
customMacros: z.nullable(z.array(z.string())).default(null),
|
||||
|
||||
/**
|
||||
* Enable a check that resets the memoization cache when the source code of the file changes.
|
||||
* This is intended to support hot module reloading (HMR), where the same runtime component
|
||||
@@ -281,18 +265,6 @@ const EnvironmentConfigSchema = z.object({
|
||||
|
||||
enableEmitHookGuards: ExternalFunctionSchema.nullish(),
|
||||
|
||||
/**
|
||||
* Enable instruction reordering. See InstructionReordering.ts for the details
|
||||
* of the approach.
|
||||
*/
|
||||
enableInstructionReordering: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enables function outlinining, where anonymous functions that do not close over
|
||||
* local variables can be extracted into top-level helper functions.
|
||||
*/
|
||||
enableFunctionOutlining: z.boolean().default(true),
|
||||
|
||||
/*
|
||||
* Enables instrumentation codegen. This emits a dev-mode only call to an
|
||||
* instrumentation function, for components and hooks that Forget compiles.
|
||||
@@ -417,23 +389,6 @@ const EnvironmentConfigSchema = z.object({
|
||||
* and identifiers have been changed.
|
||||
*/
|
||||
hookPattern: z.string().nullable().default(null),
|
||||
|
||||
/**
|
||||
* If enabled, this will treat objects named as `ref` or if their names end with the substring `Ref`,
|
||||
* and contain a property named `current`, as React refs.
|
||||
*
|
||||
* ```
|
||||
* const ref = useMyRef();
|
||||
* const myRef = useMyRef2();
|
||||
* useEffect(() => {
|
||||
* ref.current = ...;
|
||||
* myRef.current = ...;
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* Here the variables `ref` and `myRef` will be typed as Refs.
|
||||
*/
|
||||
enableTreatRefLikeIdentifiersAsRefs: z.boolean().nullable().default(false),
|
||||
});
|
||||
|
||||
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
|
||||
@@ -443,34 +398,23 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
|
||||
// Get the defaults to programmatically check for boolean properties
|
||||
const defaultConfig = EnvironmentConfigSchema.parse({});
|
||||
|
||||
for (const token of pragma.split(' ')) {
|
||||
if (!token.startsWith('@')) {
|
||||
for (const token of pragma.split(" ")) {
|
||||
if (!token.startsWith("@")) {
|
||||
continue;
|
||||
}
|
||||
const keyVal = token.slice(1);
|
||||
let [key, val]: any = keyVal.split(':');
|
||||
let [key, val]: any = keyVal.split(":");
|
||||
|
||||
if (key === 'validateNoCapitalizedCalls') {
|
||||
if (key === "validateNoCapitalizedCalls") {
|
||||
maybeConfig[key] = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
key === 'enableChangeDetectionForDebugging' &&
|
||||
(val === undefined || val === 'true')
|
||||
) {
|
||||
maybeConfig[key] = {
|
||||
source: 'react-compiler-runtime',
|
||||
importSpecifierName: '$structuralCheck',
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean') {
|
||||
if (typeof defaultConfig[key as keyof EnvironmentConfig] !== "boolean") {
|
||||
// skip parsing non-boolean properties
|
||||
continue;
|
||||
}
|
||||
if (val === undefined || val === 'true') {
|
||||
if (val === undefined || val === "true") {
|
||||
val = true;
|
||||
} else {
|
||||
val = false;
|
||||
@@ -483,7 +427,7 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
|
||||
return config.data;
|
||||
}
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Internal error, could not parse config from pragma string',
|
||||
reason: "Internal error, could not parse config from pragma string",
|
||||
description: `${fromZodError(config.error)}`,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -492,18 +436,18 @@ export function parseConfigPragma(pragma: string): EnvironmentConfig {
|
||||
|
||||
export type PartialEnvironmentConfig = Partial<EnvironmentConfig>;
|
||||
|
||||
export type ReactFunctionType = 'Component' | 'Hook' | 'Other';
|
||||
export type ReactFunctionType = "Component" | "Hook" | "Other";
|
||||
|
||||
export function printFunctionType(type: ReactFunctionType): string {
|
||||
switch (type) {
|
||||
case 'Component': {
|
||||
return 'component';
|
||||
case "Component": {
|
||||
return "component";
|
||||
}
|
||||
case 'Hook': {
|
||||
return 'hook';
|
||||
case "Hook": {
|
||||
return "hook";
|
||||
}
|
||||
default: {
|
||||
return 'function';
|
||||
return "function";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -514,11 +458,6 @@ export class Environment {
|
||||
#nextIdentifer: number = 0;
|
||||
#nextBlock: number = 0;
|
||||
#nextScope: number = 0;
|
||||
#scope: BabelScope;
|
||||
#outlinedFunctions: Array<{
|
||||
fn: HIRFunction;
|
||||
type: ReactFunctionType | null;
|
||||
}> = [];
|
||||
logger: Logger | null;
|
||||
filename: string | null;
|
||||
code: string | null;
|
||||
@@ -530,16 +469,14 @@ export class Environment {
|
||||
#hoistedIdentifiers: Set<t.Identifier>;
|
||||
|
||||
constructor(
|
||||
scope: BabelScope,
|
||||
fnType: ReactFunctionType,
|
||||
config: EnvironmentConfig,
|
||||
contextIdentifiers: Set<t.Identifier>,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
useMemoCacheIdentifier: string,
|
||||
useMemoCacheIdentifier: string
|
||||
) {
|
||||
this.#scope = scope;
|
||||
this.fnType = fnType;
|
||||
this.config = config;
|
||||
this.filename = filename;
|
||||
@@ -574,13 +511,13 @@ export class Environment {
|
||||
positionalParams: [],
|
||||
restParam: hook.effectKind,
|
||||
returnType: hook.transitiveMixedData
|
||||
? {kind: 'Object', shapeId: BuiltInMixedReadonlyId}
|
||||
: {kind: 'Poly'},
|
||||
? { kind: "Object", shapeId: BuiltInMixedReadonlyId }
|
||||
: { kind: "Poly" },
|
||||
returnValueKind: hook.valueKind,
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'Custom',
|
||||
hookKind: "Custom",
|
||||
noAlias: hook.noAlias,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -612,30 +549,12 @@ export class Environment {
|
||||
return this.#hoistedIdentifiers.has(node);
|
||||
}
|
||||
|
||||
generateGloballyUniqueIdentifierName(
|
||||
name: string | null,
|
||||
): ValidatedIdentifier {
|
||||
const identifierNode = this.#scope.generateUidIdentifier(name ?? undefined);
|
||||
return makeIdentifierName(identifierNode.name);
|
||||
}
|
||||
|
||||
outlineFunction(fn: HIRFunction, type: ReactFunctionType | null): void {
|
||||
this.#outlinedFunctions.push({fn, type});
|
||||
}
|
||||
|
||||
getOutlinedFunctions(): Array<{
|
||||
fn: HIRFunction;
|
||||
type: ReactFunctionType | null;
|
||||
}> {
|
||||
return this.#outlinedFunctions;
|
||||
}
|
||||
|
||||
getGlobalDeclaration(binding: NonLocalBinding): Global | null {
|
||||
if (this.config.hookPattern != null) {
|
||||
const match = new RegExp(this.config.hookPattern).exec(binding.name);
|
||||
if (
|
||||
match != null &&
|
||||
typeof match[1] === 'string' &&
|
||||
typeof match[1] === "string" &&
|
||||
isHookName(match[1])
|
||||
) {
|
||||
const resolvedName = match[1];
|
||||
@@ -644,17 +563,17 @@ export class Environment {
|
||||
}
|
||||
|
||||
switch (binding.kind) {
|
||||
case 'ModuleLocal': {
|
||||
case "ModuleLocal": {
|
||||
// don't resolve module locals
|
||||
return isHookName(binding.name) ? this.#getCustomHookType() : null;
|
||||
}
|
||||
case 'Global': {
|
||||
case "Global": {
|
||||
return (
|
||||
this.#globals.get(binding.name) ??
|
||||
(isHookName(binding.name) ? this.#getCustomHookType() : null)
|
||||
);
|
||||
}
|
||||
case 'ImportSpecifier': {
|
||||
case "ImportSpecifier": {
|
||||
if (this.#isKnownReactModule(binding.module)) {
|
||||
/**
|
||||
* For `import {imported as name} from "..."` form, we use the `imported`
|
||||
@@ -681,8 +600,8 @@ export class Environment {
|
||||
: null;
|
||||
}
|
||||
}
|
||||
case 'ImportDefault':
|
||||
case 'ImportNamespace': {
|
||||
case "ImportDefault":
|
||||
case "ImportNamespace": {
|
||||
if (this.#isKnownReactModule(binding.module)) {
|
||||
// only resolve imports to modules we know about
|
||||
return (
|
||||
@@ -698,19 +617,19 @@ export class Environment {
|
||||
|
||||
#isKnownReactModule(moduleName: string): boolean {
|
||||
return (
|
||||
moduleName.toLowerCase() === 'react' ||
|
||||
moduleName.toLowerCase() === 'react-dom' ||
|
||||
moduleName.toLowerCase() === "react" ||
|
||||
moduleName.toLowerCase() === "react-dom" ||
|
||||
(this.config.enableSharedRuntime__testonly &&
|
||||
moduleName === 'shared-runtime')
|
||||
moduleName === "shared-runtime")
|
||||
);
|
||||
}
|
||||
|
||||
getPropertyType(
|
||||
receiver: Type,
|
||||
property: string,
|
||||
property: string
|
||||
): BuiltInType | PolyType | null {
|
||||
let shapeId = null;
|
||||
if (receiver.kind === 'Object' || receiver.kind === 'Function') {
|
||||
if (receiver.kind === "Object" || receiver.kind === "Function") {
|
||||
shapeId = receiver.shapeId;
|
||||
}
|
||||
if (shapeId !== null) {
|
||||
@@ -726,7 +645,7 @@ export class Environment {
|
||||
suggestions: null,
|
||||
});
|
||||
let value =
|
||||
shape.properties.get(property) ?? shape.properties.get('*') ?? null;
|
||||
shape.properties.get(property) ?? shape.properties.get("*") ?? null;
|
||||
if (value === null && isHookName(property)) {
|
||||
value = this.#getCustomHookType();
|
||||
}
|
||||
@@ -739,7 +658,7 @@ export class Environment {
|
||||
}
|
||||
|
||||
getFunctionSignature(type: FunctionType): FunctionSignature | null {
|
||||
const {shapeId} = type;
|
||||
const { shapeId } = type;
|
||||
if (shapeId !== null) {
|
||||
const shape = this.#shapes.get(shapeId);
|
||||
CompilerError.invariant(shape !== undefined, {
|
||||
@@ -773,7 +692,7 @@ export function isHookName(name: string): boolean {
|
||||
}
|
||||
|
||||
export function parseEnvironmentConfig(
|
||||
partialConfig: PartialEnvironmentConfig,
|
||||
partialConfig: PartialEnvironmentConfig
|
||||
): Result<EnvironmentConfig, ZodError<PartialEnvironmentConfig>> {
|
||||
const config = EnvironmentConfigSchema.safeParse(partialConfig);
|
||||
if (config.success) {
|
||||
@@ -784,7 +703,7 @@ export function parseEnvironmentConfig(
|
||||
}
|
||||
|
||||
export function validateEnvironmentConfig(
|
||||
partialConfig: PartialEnvironmentConfig,
|
||||
partialConfig: PartialEnvironmentConfig
|
||||
): EnvironmentConfig {
|
||||
const config = EnvironmentConfigSchema.safeParse(partialConfig);
|
||||
if (config.success) {
|
||||
@@ -793,7 +712,7 @@ export function validateEnvironmentConfig(
|
||||
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason:
|
||||
'Could not validate environment config. Update React Compiler config to fix the error',
|
||||
"Could not validate environment config. Update React Compiler config to fix the error",
|
||||
description: `${fromZodError(config.error)}`,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -801,10 +720,10 @@ export function validateEnvironmentConfig(
|
||||
}
|
||||
|
||||
export function tryParseExternalFunction(
|
||||
maybeExternalFunction: any,
|
||||
maybeExternalFunction: any
|
||||
): ExternalFunction {
|
||||
const externalFunction = ExternalFunctionSchema.safeParse(
|
||||
maybeExternalFunction,
|
||||
maybeExternalFunction
|
||||
);
|
||||
if (externalFunction.success) {
|
||||
return externalFunction.data;
|
||||
@@ -812,7 +731,7 @@ export function tryParseExternalFunction(
|
||||
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason:
|
||||
'Could not parse external function. Update React Compiler config to fix the error',
|
||||
"Could not parse external function. Update React Compiler config to fix the error",
|
||||
description: `${fromZodError(externalFunction.error)}`,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
import type * as t from '@babel/types';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
import {GeneratedSource} from './HIR';
|
||||
import type { NodePath } from "@babel/traverse";
|
||||
import type * as t from "@babel/types";
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { getOrInsertDefault } from "../Utils/utils";
|
||||
import { GeneratedSource } from "./HIR";
|
||||
|
||||
type IdentifierInfo = {
|
||||
reassigned: boolean;
|
||||
@@ -35,7 +35,7 @@ type FindContextIdentifierState = {
|
||||
const withFunctionScope = {
|
||||
enter: function (
|
||||
path: BabelFunction,
|
||||
state: FindContextIdentifierState,
|
||||
state: FindContextIdentifierState
|
||||
): void {
|
||||
state.currentFn.push(path);
|
||||
},
|
||||
@@ -45,7 +45,7 @@ const withFunctionScope = {
|
||||
};
|
||||
|
||||
export function findContextIdentifiers(
|
||||
func: NodePath<t.Function>,
|
||||
func: NodePath<t.Function>
|
||||
): Set<t.Identifier> {
|
||||
const state: FindContextIdentifierState = {
|
||||
currentFn: [],
|
||||
@@ -60,17 +60,17 @@ export function findContextIdentifiers(
|
||||
ObjectMethod: withFunctionScope,
|
||||
AssignmentExpression(
|
||||
path: NodePath<t.AssignmentExpression>,
|
||||
state: FindContextIdentifierState,
|
||||
state: FindContextIdentifierState
|
||||
): void {
|
||||
const left = path.get('left');
|
||||
const left = path.get("left");
|
||||
const currentFn = state.currentFn.at(-1) ?? null;
|
||||
handleAssignment(currentFn, state.identifiers, left);
|
||||
},
|
||||
UpdateExpression(
|
||||
path: NodePath<t.UpdateExpression>,
|
||||
state: FindContextIdentifierState,
|
||||
state: FindContextIdentifierState
|
||||
): void {
|
||||
const argument = path.get('argument');
|
||||
const argument = path.get("argument");
|
||||
const currentFn = state.currentFn.at(-1) ?? null;
|
||||
if (argument.isLVal()) {
|
||||
handleAssignment(currentFn, state.identifiers, argument);
|
||||
@@ -78,7 +78,7 @@ export function findContextIdentifiers(
|
||||
},
|
||||
Identifier(
|
||||
path: NodePath<t.Identifier>,
|
||||
state: FindContextIdentifierState,
|
||||
state: FindContextIdentifierState
|
||||
): void {
|
||||
const currentFn = state.currentFn.at(-1) ?? null;
|
||||
if (path.isReferencedIdentifier()) {
|
||||
@@ -86,7 +86,7 @@ export function findContextIdentifiers(
|
||||
}
|
||||
},
|
||||
},
|
||||
state,
|
||||
state
|
||||
);
|
||||
|
||||
const result = new Set<t.Identifier>();
|
||||
@@ -103,7 +103,7 @@ export function findContextIdentifiers(
|
||||
function handleIdentifier(
|
||||
currentFn: BabelFunction | null,
|
||||
identifiers: Map<t.Identifier, IdentifierInfo>,
|
||||
path: NodePath<t.Identifier>,
|
||||
path: NodePath<t.Identifier>
|
||||
): void {
|
||||
const name = path.node.name;
|
||||
const binding = path.scope.getBinding(name);
|
||||
@@ -126,7 +126,7 @@ function handleIdentifier(
|
||||
function handleAssignment(
|
||||
currentFn: BabelFunction | null,
|
||||
identifiers: Map<t.Identifier, IdentifierInfo>,
|
||||
lvalPath: NodePath<t.LVal>,
|
||||
lvalPath: NodePath<t.LVal>
|
||||
): void {
|
||||
/*
|
||||
* Find all reassignments to identifiers declared outside of currentFn
|
||||
@@ -134,7 +134,7 @@ function handleAssignment(
|
||||
*/
|
||||
const lvalNode = lvalPath.node;
|
||||
switch (lvalNode.type) {
|
||||
case 'Identifier': {
|
||||
case "Identifier": {
|
||||
const path = lvalPath as NodePath<t.Identifier>;
|
||||
const name = path.node.name;
|
||||
const binding = path.scope.getBinding(name);
|
||||
@@ -155,20 +155,20 @@ function handleAssignment(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ArrayPattern': {
|
||||
case "ArrayPattern": {
|
||||
const path = lvalPath as NodePath<t.ArrayPattern>;
|
||||
for (const element of path.get('elements')) {
|
||||
for (const element of path.get("elements")) {
|
||||
if (nonNull(element)) {
|
||||
handleAssignment(currentFn, identifiers, element);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ObjectPattern': {
|
||||
case "ObjectPattern": {
|
||||
const path = lvalPath as NodePath<t.ObjectPattern>;
|
||||
for (const property of path.get('properties')) {
|
||||
for (const property of path.get("properties")) {
|
||||
if (property.isObjectProperty()) {
|
||||
const valuePath = property.get('value');
|
||||
const valuePath = property.get("value");
|
||||
CompilerError.invariant(valuePath.isLVal(), {
|
||||
reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,
|
||||
description: null,
|
||||
@@ -188,18 +188,18 @@ function handleAssignment(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'AssignmentPattern': {
|
||||
case "AssignmentPattern": {
|
||||
const path = lvalPath as NodePath<t.AssignmentPattern>;
|
||||
const left = path.get('left');
|
||||
const left = path.get("left");
|
||||
handleAssignment(currentFn, identifiers, left);
|
||||
break;
|
||||
}
|
||||
case 'RestElement': {
|
||||
case "RestElement": {
|
||||
const path = lvalPath as NodePath<t.RestElement>;
|
||||
handleAssignment(currentFn, identifiers, path.get('argument'));
|
||||
handleAssignment(currentFn, identifiers, path.get("argument"));
|
||||
break;
|
||||
}
|
||||
case 'MemberExpression': {
|
||||
case "MemberExpression": {
|
||||
// Interior mutability (not a reassign)
|
||||
break;
|
||||
}
|
||||
@@ -215,7 +215,7 @@ function handleAssignment(
|
||||
}
|
||||
|
||||
function nonNull<T extends NonNullable<t.Node>>(
|
||||
t: NodePath<T | null>,
|
||||
t: NodePath<T | null>
|
||||
): t is NodePath<T> {
|
||||
return t.node != null;
|
||||
}
|
||||
|
||||
@@ -5,24 +5,22 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import { Effect, ValueKind, ValueReason } from "./HIR";
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInUseActionStateId,
|
||||
BuiltInUseEffectHookId,
|
||||
BuiltInUseInsertionEffectHookId,
|
||||
BuiltInUseLayoutEffectHookId,
|
||||
BuiltInUseOperatorId,
|
||||
BuiltInUseReducerId,
|
||||
BuiltInUseRefId,
|
||||
BuiltInUseStateId,
|
||||
ShapeRegistry,
|
||||
addFunction,
|
||||
addHook,
|
||||
addObject,
|
||||
} from './ObjectShape';
|
||||
import {BuiltInType, PolyType} from './Types';
|
||||
} from "./ObjectShape";
|
||||
import { BuiltInType, PolyType } from "./Types";
|
||||
|
||||
/*
|
||||
* This file exports types and defaults for JavaScript global objects.
|
||||
@@ -37,59 +35,59 @@ 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',
|
||||
'Uint16Array',
|
||||
'Uint32Array',
|
||||
'ArrayBuffer',
|
||||
'JSON',
|
||||
'parseFloat',
|
||||
'parseInt',
|
||||
'console',
|
||||
'isNaN',
|
||||
'eval',
|
||||
'isFinite',
|
||||
'encodeURI',
|
||||
'decodeURI',
|
||||
'encodeURIComponent',
|
||||
'decodeURIComponent',
|
||||
"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",
|
||||
"Uint16Array",
|
||||
"Uint32Array",
|
||||
"ArrayBuffer",
|
||||
"JSON",
|
||||
"parseFloat",
|
||||
"parseInt",
|
||||
"console",
|
||||
"isNaN",
|
||||
"eval",
|
||||
"isFinite",
|
||||
"encodeURI",
|
||||
"decodeURI",
|
||||
"encodeURIComponent",
|
||||
"decodeURIComponent",
|
||||
]);
|
||||
|
||||
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
[
|
||||
'Array',
|
||||
addObject(DEFAULT_SHAPES, 'Array', [
|
||||
"Array",
|
||||
addObject(DEFAULT_SHAPES, "Array", [
|
||||
[
|
||||
'isArray',
|
||||
"isArray",
|
||||
// Array.isArray(value)
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
@@ -106,12 +104,12 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
* function)
|
||||
*/
|
||||
[
|
||||
'of',
|
||||
"of",
|
||||
// Array.of(element0, ..., elementN)
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
returnType: { kind: "Object", shapeId: BuiltInArrayId },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
@@ -119,85 +117,85 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
]),
|
||||
],
|
||||
[
|
||||
'Math',
|
||||
addObject(DEFAULT_SHAPES, 'Math', [
|
||||
"Math",
|
||||
addObject(DEFAULT_SHAPES, "Math", [
|
||||
// Static properties (TODO)
|
||||
['PI', {kind: 'Primitive'}],
|
||||
["PI", { kind: "Primitive" }],
|
||||
// Static methods (TODO)
|
||||
[
|
||||
'max',
|
||||
"max",
|
||||
// Math.max(value0, ..., valueN)
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
]),
|
||||
],
|
||||
['Infinity', {kind: 'Primitive'}],
|
||||
['NaN', {kind: 'Primitive'}],
|
||||
["Infinity", { kind: "Primitive" }],
|
||||
["NaN", { kind: "Primitive" }],
|
||||
[
|
||||
'console',
|
||||
addObject(DEFAULT_SHAPES, 'console', [
|
||||
"console",
|
||||
addObject(DEFAULT_SHAPES, "console", [
|
||||
[
|
||||
'error',
|
||||
"error",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'info',
|
||||
"info",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'log',
|
||||
"log",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'table',
|
||||
"table",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'trace',
|
||||
"trace",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'warn',
|
||||
"warn",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
@@ -205,31 +203,31 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
]),
|
||||
],
|
||||
[
|
||||
'Boolean',
|
||||
"Boolean",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'Number',
|
||||
"Number",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'String',
|
||||
"String",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
@@ -244,179 +242,155 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
*/
|
||||
const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
[
|
||||
'useContext',
|
||||
"useContext",
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useContext',
|
||||
hookKind: "useContext",
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.Context,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useState',
|
||||
"useState",
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseStateId},
|
||||
returnType: { kind: "Object", shapeId: BuiltInUseStateId },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useState',
|
||||
hookKind: "useState",
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useActionState',
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseActionStateId},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useActionState',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useReducer',
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseReducerId},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useReducer',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.ReducerState,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useRef',
|
||||
"useRef",
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Capture,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseRefId},
|
||||
returnType: { kind: "Object", shapeId: BuiltInUseRefId },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useRef',
|
||||
hookKind: "useRef",
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useMemo',
|
||||
"useMemo",
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useMemo',
|
||||
hookKind: "useMemo",
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useCallback',
|
||||
"useCallback",
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useCallback',
|
||||
hookKind: "useCallback",
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useEffect',
|
||||
"useEffect",
|
||||
addHook(
|
||||
DEFAULT_SHAPES,
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Primitive'},
|
||||
returnType: { kind: "Primitive" },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useEffect',
|
||||
hookKind: "useEffect",
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
BuiltInUseEffectHookId,
|
||||
BuiltInUseEffectHookId
|
||||
),
|
||||
],
|
||||
[
|
||||
'useLayoutEffect',
|
||||
"useLayoutEffect",
|
||||
addHook(
|
||||
DEFAULT_SHAPES,
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useLayoutEffect',
|
||||
hookKind: "useLayoutEffect",
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
BuiltInUseLayoutEffectHookId,
|
||||
BuiltInUseLayoutEffectHookId
|
||||
),
|
||||
],
|
||||
[
|
||||
'useInsertionEffect',
|
||||
"useInsertionEffect",
|
||||
addHook(
|
||||
DEFAULT_SHAPES,
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useInsertionEffect',
|
||||
hookKind: "useLayoutEffect",
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
BuiltInUseInsertionEffectHookId,
|
||||
BuiltInUseInsertionEffectHookId
|
||||
),
|
||||
],
|
||||
[
|
||||
'use',
|
||||
"use",
|
||||
addFunction(
|
||||
DEFAULT_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
BuiltInUseOperatorId,
|
||||
BuiltInUseOperatorId
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
TYPED_GLOBALS.push(
|
||||
[
|
||||
'React',
|
||||
"React",
|
||||
addObject(DEFAULT_SHAPES, null, [
|
||||
...REACT_APIS,
|
||||
[
|
||||
'createElement',
|
||||
"createElement",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'cloneElement',
|
||||
"cloneElement",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'createRef',
|
||||
"createRef",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Capture, // createRef takes no paramters
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseRefId},
|
||||
returnType: { kind: "Object", shapeId: BuiltInUseRefId },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
@@ -424,15 +398,15 @@ TYPED_GLOBALS.push(
|
||||
]),
|
||||
],
|
||||
[
|
||||
'_jsx',
|
||||
"_jsx",
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
}),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
export type Global = BuiltInType | PolyType;
|
||||
@@ -442,7 +416,7 @@ export const DEFAULT_GLOBALS: GlobalRegistry = new Map(REACT_APIS);
|
||||
// Hack until we add ObjectShapes for all globals
|
||||
for (const name of UNTYPED_GLOBALS) {
|
||||
DEFAULT_GLOBALS.set(name, {
|
||||
kind: 'Poly',
|
||||
kind: "Poly",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -452,22 +426,22 @@ for (const [name, type_] of TYPED_GLOBALS) {
|
||||
|
||||
// Recursive global type
|
||||
DEFAULT_GLOBALS.set(
|
||||
'globalThis',
|
||||
addObject(DEFAULT_SHAPES, 'globalThis', TYPED_GLOBALS),
|
||||
"globalThis",
|
||||
addObject(DEFAULT_SHAPES, "globalThis", TYPED_GLOBALS)
|
||||
);
|
||||
|
||||
export function installReAnimatedTypes(
|
||||
globals: GlobalRegistry,
|
||||
registry: ShapeRegistry,
|
||||
registry: ShapeRegistry
|
||||
): void {
|
||||
// hooks that freeze args and return frozen value
|
||||
const frozenHooks = [
|
||||
'useFrameCallback',
|
||||
'useAnimatedStyle',
|
||||
'useAnimatedProps',
|
||||
'useAnimatedScrollHandler',
|
||||
'useAnimatedReaction',
|
||||
'useWorkletCallback',
|
||||
"useFrameCallback",
|
||||
"useAnimatedStyle",
|
||||
"useAnimatedProps",
|
||||
"useAnimatedScrollHandler",
|
||||
"useAnimatedReaction",
|
||||
"useWorkletCallback",
|
||||
];
|
||||
for (const hook of frozenHooks) {
|
||||
globals.set(
|
||||
@@ -475,12 +449,12 @@ export function installReAnimatedTypes(
|
||||
addHook(registry, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
noAlias: true,
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'Custom',
|
||||
}),
|
||||
hookKind: "Custom",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -488,31 +462,31 @@ export function installReAnimatedTypes(
|
||||
* hooks that return a mutable value. ideally these should be modelled as a
|
||||
* ref, but this works for now.
|
||||
*/
|
||||
const mutableHooks = ['useSharedValue', 'useDerivedValue'];
|
||||
const mutableHooks = ["useSharedValue", "useDerivedValue"];
|
||||
for (const hook of mutableHooks) {
|
||||
globals.set(
|
||||
hook,
|
||||
addHook(registry, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'Custom',
|
||||
}),
|
||||
hookKind: "Custom",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// functions that return mutable value
|
||||
const funcs = [
|
||||
'withTiming',
|
||||
'withSpring',
|
||||
'createAnimatedPropAdapter',
|
||||
'withDecay',
|
||||
'withRepeat',
|
||||
'runOnUI',
|
||||
'executeOnUIRuntimeSync',
|
||||
"withTiming",
|
||||
"withSpring",
|
||||
"createAnimatedPropAdapter",
|
||||
"withDecay",
|
||||
"withRepeat",
|
||||
"runOnUI",
|
||||
"executeOnUIRuntimeSync",
|
||||
];
|
||||
for (const fn of funcs) {
|
||||
globals.set(
|
||||
@@ -520,11 +494,11 @@ export function installReAnimatedTypes(
|
||||
addFunction(registry, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Poly'},
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Binding, NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Environment} from './Environment';
|
||||
import { Binding, NodePath } from "@babel/traverse";
|
||||
import * as t from "@babel/types";
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { Environment } from "./Environment";
|
||||
import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
@@ -28,13 +28,13 @@ import {
|
||||
makeIdentifierName,
|
||||
makeInstructionId,
|
||||
makeType,
|
||||
} from './HIR';
|
||||
import {printInstruction} from './PrintHIR';
|
||||
} from "./HIR";
|
||||
import { printInstruction } from "./PrintHIR";
|
||||
import {
|
||||
eachTerminalSuccessor,
|
||||
mapTerminalSuccessors,
|
||||
terminalFallthrough,
|
||||
} from './visitors';
|
||||
} from "./visitors";
|
||||
|
||||
/*
|
||||
* *******************************************************************************************
|
||||
@@ -54,31 +54,31 @@ export type WipBlock = {
|
||||
type Scope = LoopScope | LabelScope | SwitchScope;
|
||||
|
||||
type LoopScope = {
|
||||
kind: 'loop';
|
||||
kind: "loop";
|
||||
label: string | null;
|
||||
continueBlock: BlockId;
|
||||
breakBlock: BlockId;
|
||||
};
|
||||
|
||||
type SwitchScope = {
|
||||
kind: 'switch';
|
||||
kind: "switch";
|
||||
breakBlock: BlockId;
|
||||
label: string | null;
|
||||
};
|
||||
|
||||
type LabelScope = {
|
||||
kind: 'label';
|
||||
kind: "label";
|
||||
label: string;
|
||||
breakBlock: BlockId;
|
||||
};
|
||||
|
||||
function newBlock(id: BlockId, kind: BlockKind): WipBlock {
|
||||
return {id, kind, instructions: []};
|
||||
return { id, kind, instructions: [] };
|
||||
}
|
||||
|
||||
export type Bindings = Map<
|
||||
string,
|
||||
{node: t.Identifier; identifier: Identifier}
|
||||
{ node: t.Identifier; identifier: Identifier }
|
||||
>;
|
||||
|
||||
/*
|
||||
@@ -90,13 +90,13 @@ export type ExceptionsMode =
|
||||
* Mode used for code not covered by explicit exception handling, any
|
||||
* errors are assumed to be thrown out of the function
|
||||
*/
|
||||
| {kind: 'ThrowExceptions'}
|
||||
| { kind: "ThrowExceptions" }
|
||||
/*
|
||||
* Mode used for code that *is* covered by explicit exception handling
|
||||
* (ie try/catch), which requires modeling the possibility of control
|
||||
* flow to the exception handler.
|
||||
*/
|
||||
| {kind: 'CatchExceptions'; handler: BlockId};
|
||||
| { kind: "CatchExceptions"; handler: BlockId };
|
||||
|
||||
// Helper class for constructing a CFG
|
||||
export default class HIRBuilder {
|
||||
@@ -110,11 +110,6 @@ export default class HIRBuilder {
|
||||
#exceptionHandlerStack: Array<BlockId> = [];
|
||||
parentFunction: NodePath<t.Function>;
|
||||
errors: CompilerError = new CompilerError();
|
||||
/**
|
||||
* Traversal context: counts the number of `fbt` tag parents
|
||||
* of the current babel node.
|
||||
*/
|
||||
fbtDepth: number = 0;
|
||||
|
||||
get nextIdentifierId(): IdentifierId {
|
||||
return this.#env.nextIdentifierId;
|
||||
@@ -136,14 +131,14 @@ export default class HIRBuilder {
|
||||
env: Environment,
|
||||
parentFunction: NodePath<t.Function>, // the outermost function being compiled
|
||||
bindings: Bindings | null = null,
|
||||
context: Array<t.Identifier> | null = null,
|
||||
context: Array<t.Identifier> | null = null
|
||||
) {
|
||||
this.#env = env;
|
||||
this.#bindings = bindings ?? new Map();
|
||||
this.parentFunction = parentFunction;
|
||||
this.#context = context ?? [];
|
||||
this.#entry = makeBlockId(env.nextBlockId);
|
||||
this.#current = newBlock(this.#entry, 'block');
|
||||
this.#current = newBlock(this.#entry, "block");
|
||||
}
|
||||
|
||||
currentBlockKind(): BlockKind {
|
||||
@@ -158,13 +153,13 @@ export default class HIRBuilder {
|
||||
const continuationBlock = this.reserve(this.currentBlockKind());
|
||||
this.terminateWithContinuation(
|
||||
{
|
||||
kind: 'maybe-throw',
|
||||
kind: "maybe-throw",
|
||||
continuation: continuationBlock.id,
|
||||
handler: exceptionHandler,
|
||||
id: makeInstructionId(0),
|
||||
loc: instruction.loc,
|
||||
},
|
||||
continuationBlock,
|
||||
continuationBlock
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -185,7 +180,7 @@ export default class HIRBuilder {
|
||||
return {
|
||||
id,
|
||||
name: null,
|
||||
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
|
||||
mutableRange: { start: makeInstructionId(0), end: makeInstructionId(0) },
|
||||
scope: null,
|
||||
type: makeType(),
|
||||
loc,
|
||||
@@ -193,7 +188,7 @@ export default class HIRBuilder {
|
||||
}
|
||||
|
||||
#resolveBabelBinding(
|
||||
path: NodePath<t.Identifier | t.JSXIdentifier>,
|
||||
path: NodePath<t.Identifier | t.JSXIdentifier>
|
||||
): Binding | null {
|
||||
const originalName = path.node.name;
|
||||
const binding = path.scope.getBinding(originalName);
|
||||
@@ -234,12 +229,12 @@ export default class HIRBuilder {
|
||||
* ```
|
||||
*/
|
||||
resolveIdentifier(
|
||||
path: NodePath<t.Identifier | t.JSXIdentifier>,
|
||||
path: NodePath<t.Identifier | t.JSXIdentifier>
|
||||
): VariableBinding {
|
||||
const originalName = path.node.name;
|
||||
const babelBinding = this.#resolveBabelBinding(path);
|
||||
if (babelBinding == null) {
|
||||
return {kind: 'Global', name: originalName};
|
||||
return { kind: "Global", name: originalName };
|
||||
}
|
||||
|
||||
// Check if the binding is from module scope
|
||||
@@ -251,7 +246,7 @@ export default class HIRBuilder {
|
||||
const importDeclaration =
|
||||
path.parentPath as NodePath<t.ImportDeclaration>;
|
||||
return {
|
||||
kind: 'ImportDefault',
|
||||
kind: "ImportDefault",
|
||||
name: originalName,
|
||||
module: importDeclaration.node.source.value,
|
||||
};
|
||||
@@ -259,11 +254,11 @@ export default class HIRBuilder {
|
||||
const importDeclaration =
|
||||
path.parentPath as NodePath<t.ImportDeclaration>;
|
||||
return {
|
||||
kind: 'ImportSpecifier',
|
||||
kind: "ImportSpecifier",
|
||||
name: originalName,
|
||||
module: importDeclaration.node.source.value,
|
||||
imported:
|
||||
path.node.imported.type === 'Identifier'
|
||||
path.node.imported.type === "Identifier"
|
||||
? path.node.imported.name
|
||||
: path.node.imported.value,
|
||||
};
|
||||
@@ -271,13 +266,13 @@ export default class HIRBuilder {
|
||||
const importDeclaration =
|
||||
path.parentPath as NodePath<t.ImportDeclaration>;
|
||||
return {
|
||||
kind: 'ImportNamespace',
|
||||
kind: "ImportNamespace",
|
||||
name: originalName,
|
||||
module: importDeclaration.node.source.value,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
kind: 'ModuleLocal',
|
||||
kind: "ModuleLocal",
|
||||
name: originalName,
|
||||
};
|
||||
}
|
||||
@@ -288,7 +283,7 @@ export default class HIRBuilder {
|
||||
babelBinding.scope.rename(originalName, resolvedBinding.name.value);
|
||||
}
|
||||
return {
|
||||
kind: 'Identifier',
|
||||
kind: "Identifier",
|
||||
identifier: resolvedBinding,
|
||||
bindingKind: babelBinding.kind,
|
||||
};
|
||||
@@ -299,7 +294,7 @@ export default class HIRBuilder {
|
||||
if (binding) {
|
||||
// Check if the binding is from module scope, if so return null
|
||||
const outerBinding = this.parentFunction.scope.parent.getBinding(
|
||||
path.node.name,
|
||||
path.node.name
|
||||
);
|
||||
if (binding === outerBinding) {
|
||||
return false;
|
||||
@@ -311,12 +306,6 @@ export default class HIRBuilder {
|
||||
}
|
||||
|
||||
resolveBinding(node: t.Identifier): Identifier {
|
||||
if (node.name === 'fbt') {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'Support local variables named "fbt"',
|
||||
loc: node.loc ?? null,
|
||||
});
|
||||
}
|
||||
const originalName = node.name;
|
||||
let name = originalName;
|
||||
let index = 0;
|
||||
@@ -335,7 +324,7 @@ export default class HIRBuilder {
|
||||
type: makeType(),
|
||||
loc: node.loc ?? GeneratedSource,
|
||||
};
|
||||
this.#bindings.set(name, {node, identifier});
|
||||
this.#bindings.set(name, { node, identifier });
|
||||
return identifier;
|
||||
} else if (mapping.node === node) {
|
||||
return mapping.identifier;
|
||||
@@ -356,7 +345,7 @@ export default class HIRBuilder {
|
||||
if (
|
||||
!rpoBlocks.has(id) &&
|
||||
block.instructions.some(
|
||||
instr => instr.value.kind === 'FunctionExpression',
|
||||
(instr) => instr.value.kind === "FunctionExpression"
|
||||
)
|
||||
) {
|
||||
CompilerError.throwTodo({
|
||||
@@ -380,7 +369,7 @@ export default class HIRBuilder {
|
||||
|
||||
// Terminate the current block w the given terminal, and start a new block
|
||||
terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void {
|
||||
const {id: blockId, kind, instructions} = this.#current;
|
||||
const { id: blockId, kind, instructions } = this.#current;
|
||||
this.#completed.set(blockId, {
|
||||
kind,
|
||||
id: blockId,
|
||||
@@ -400,7 +389,7 @@ export default class HIRBuilder {
|
||||
* reserved block as the new current block
|
||||
*/
|
||||
terminateWithContinuation(terminal: Terminal, continuation: WipBlock): void {
|
||||
const {id: blockId, kind, instructions} = this.#current;
|
||||
const { id: blockId, kind, instructions } = this.#current;
|
||||
this.#completed.set(blockId, {
|
||||
kind: kind,
|
||||
id: blockId,
|
||||
@@ -423,7 +412,7 @@ export default class HIRBuilder {
|
||||
|
||||
// Save a previously reserved block as completed
|
||||
complete(block: WipBlock, terminal: Terminal): void {
|
||||
const {id: blockId, kind, instructions} = block;
|
||||
const { id: blockId, kind, instructions } = block;
|
||||
this.#completed.set(blockId, {
|
||||
kind,
|
||||
id: blockId,
|
||||
@@ -442,7 +431,7 @@ export default class HIRBuilder {
|
||||
const current = this.#current;
|
||||
this.#current = wip;
|
||||
const terminal = fn();
|
||||
const {id: blockId, kind, instructions} = this.#current;
|
||||
const { id: blockId, kind, instructions } = this.#current;
|
||||
this.#completed.set(blockId, {
|
||||
kind,
|
||||
id: blockId,
|
||||
@@ -470,7 +459,7 @@ export default class HIRBuilder {
|
||||
|
||||
label<T>(label: string, breakBlock: BlockId, fn: () => T): T {
|
||||
this.#scopes.push({
|
||||
kind: 'label',
|
||||
kind: "label",
|
||||
breakBlock,
|
||||
label,
|
||||
});
|
||||
@@ -478,22 +467,22 @@ export default class HIRBuilder {
|
||||
const last = this.#scopes.pop();
|
||||
CompilerError.invariant(
|
||||
last != null &&
|
||||
last.kind === 'label' &&
|
||||
last.kind === "label" &&
|
||||
last.label === label &&
|
||||
last.breakBlock === breakBlock,
|
||||
{
|
||||
reason: 'Mismatched label',
|
||||
reason: "Mismatched label",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
switch<T>(label: string | null, breakBlock: BlockId, fn: () => T): T {
|
||||
this.#scopes.push({
|
||||
kind: 'switch',
|
||||
kind: "switch",
|
||||
breakBlock,
|
||||
label,
|
||||
});
|
||||
@@ -501,15 +490,15 @@ export default class HIRBuilder {
|
||||
const last = this.#scopes.pop();
|
||||
CompilerError.invariant(
|
||||
last != null &&
|
||||
last.kind === 'switch' &&
|
||||
last.kind === "switch" &&
|
||||
last.label === label &&
|
||||
last.breakBlock === breakBlock,
|
||||
{
|
||||
reason: 'Mismatched label',
|
||||
reason: "Mismatched label",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
return value;
|
||||
}
|
||||
@@ -524,10 +513,10 @@ export default class HIRBuilder {
|
||||
continueBlock: BlockId,
|
||||
// block following the loop. "break" jumps here.
|
||||
breakBlock: BlockId,
|
||||
fn: () => T,
|
||||
fn: () => T
|
||||
): T {
|
||||
this.#scopes.push({
|
||||
kind: 'loop',
|
||||
kind: "loop",
|
||||
label,
|
||||
continueBlock,
|
||||
breakBlock,
|
||||
@@ -536,16 +525,16 @@ export default class HIRBuilder {
|
||||
const last = this.#scopes.pop();
|
||||
CompilerError.invariant(
|
||||
last != null &&
|
||||
last.kind === 'loop' &&
|
||||
last.kind === "loop" &&
|
||||
last.label === label &&
|
||||
last.continueBlock === continueBlock &&
|
||||
last.breakBlock === breakBlock,
|
||||
{
|
||||
reason: 'Mismatched loops',
|
||||
reason: "Mismatched loops",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
},
|
||||
}
|
||||
);
|
||||
return value;
|
||||
}
|
||||
@@ -559,14 +548,14 @@ export default class HIRBuilder {
|
||||
const scope = this.#scopes[ii];
|
||||
if (
|
||||
(label === null &&
|
||||
(scope.kind === 'loop' || scope.kind === 'switch')) ||
|
||||
(scope.kind === "loop" || scope.kind === "switch")) ||
|
||||
label === scope.label
|
||||
) {
|
||||
return scope.breakBlock;
|
||||
}
|
||||
}
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Expected a loop or switch to be in scope',
|
||||
reason: "Expected a loop or switch to be in scope",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -581,13 +570,13 @@ export default class HIRBuilder {
|
||||
lookupContinue(label: string | null): BlockId {
|
||||
for (let ii = this.#scopes.length - 1; ii >= 0; ii--) {
|
||||
const scope = this.#scopes[ii];
|
||||
if (scope.kind === 'loop') {
|
||||
if (scope.kind === "loop") {
|
||||
if (label === null || label === scope.label) {
|
||||
return scope.continueBlock;
|
||||
}
|
||||
} else if (label !== null && scope.label === label) {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Continue may only refer to a labeled loop',
|
||||
reason: "Continue may only refer to a labeled loop",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -595,7 +584,7 @@ export default class HIRBuilder {
|
||||
}
|
||||
}
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Expected a loop to be in scope',
|
||||
reason: "Expected a loop to be in scope",
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -644,7 +633,7 @@ function _shrink(func: HIR): void {
|
||||
}
|
||||
reachable.add(blockId);
|
||||
const block = func.blocks.get(blockId)!;
|
||||
block.terminal = mapTerminalSuccessors(block.terminal, prevTarget => {
|
||||
block.terminal = mapTerminalSuccessors(block.terminal, (prevTarget) => {
|
||||
const target = resolveBlockTarget(prevTarget);
|
||||
queue.push(target);
|
||||
return target;
|
||||
@@ -660,7 +649,7 @@ function _shrink(func: HIR): void {
|
||||
export function removeUnreachableForUpdates(fn: HIR): void {
|
||||
for (const [, block] of fn.blocks) {
|
||||
if (
|
||||
block.terminal.kind === 'for' &&
|
||||
block.terminal.kind === "for" &&
|
||||
block.terminal.update !== null &&
|
||||
!fn.blocks.has(block.terminal.update)
|
||||
) {
|
||||
@@ -681,10 +670,10 @@ export function removeDeadDoWhileStatements(func: HIR): void {
|
||||
* MergeConsecutiveBlocks figures out how to merge as appropriate.
|
||||
*/
|
||||
for (const [_, block] of func.blocks) {
|
||||
if (block.terminal.kind === 'do-while') {
|
||||
if (block.terminal.kind === "do-while") {
|
||||
if (!visited.has(block.terminal.test)) {
|
||||
block.terminal = {
|
||||
kind: 'goto',
|
||||
kind: "goto",
|
||||
block: block.terminal.loop,
|
||||
variant: GotoVariant.Break,
|
||||
id: block.terminal.id,
|
||||
@@ -711,7 +700,7 @@ export function reversePostorderBlocks(func: HIR): void {
|
||||
* may be in the output: blocks will be removed in the case of unreachable code in
|
||||
* the input.
|
||||
*/
|
||||
function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
|
||||
function getReversePostorderedBlocks(func: HIR): HIR["blocks"] {
|
||||
const visited: Set<BlockId> = new Set();
|
||||
const used: Set<BlockId> = new Set();
|
||||
const usedFallthroughs: Set<BlockId> = new Set();
|
||||
@@ -783,7 +772,7 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
|
||||
...block,
|
||||
instructions: [],
|
||||
terminal: {
|
||||
kind: 'unreachable',
|
||||
kind: "unreachable",
|
||||
id: block.terminal.id,
|
||||
loc: block.terminal.loc,
|
||||
},
|
||||
@@ -824,7 +813,7 @@ export function markPredecessors(func: HIR): void {
|
||||
return;
|
||||
}
|
||||
CompilerError.invariant(block != null, {
|
||||
reason: 'unexpected missing block',
|
||||
reason: "unexpected missing block",
|
||||
description: `block ${blockId}`,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
@@ -837,7 +826,7 @@ export function markPredecessors(func: HIR): void {
|
||||
}
|
||||
visited.add(blockId);
|
||||
|
||||
const {terminal} = block;
|
||||
const { terminal } = block;
|
||||
|
||||
for (const successor of eachTerminalSuccessor(terminal)) {
|
||||
visit(successor, block);
|
||||
@@ -852,7 +841,7 @@ export function markPredecessors(func: HIR): void {
|
||||
*/
|
||||
function getTargetIfIndirection(block: BasicBlock): number | null {
|
||||
return block.instructions.length === 0 &&
|
||||
block.terminal.kind === 'goto' &&
|
||||
block.terminal.kind === "goto" &&
|
||||
block.terminal.variant === GotoVariant.Break
|
||||
? block.terminal.block
|
||||
: null;
|
||||
@@ -865,14 +854,14 @@ function getTargetIfIndirection(block: BasicBlock): number | null {
|
||||
export function removeUnnecessaryTryCatch(fn: HIR): void {
|
||||
for (const [, block] of fn.blocks) {
|
||||
if (
|
||||
block.terminal.kind === 'try' &&
|
||||
block.terminal.kind === "try" &&
|
||||
!fn.blocks.has(block.terminal.handler)
|
||||
) {
|
||||
const handlerId = block.terminal.handler;
|
||||
const fallthroughId = block.terminal.fallthrough;
|
||||
const fallthrough = fn.blocks.get(fallthroughId);
|
||||
block.terminal = {
|
||||
kind: 'goto',
|
||||
kind: "goto",
|
||||
block: block.terminal.block,
|
||||
id: makeInstructionId(0),
|
||||
loc: block.terminal.loc,
|
||||
@@ -893,13 +882,13 @@ export function removeUnnecessaryTryCatch(fn: HIR): void {
|
||||
|
||||
export function createTemporaryPlace(
|
||||
env: Environment,
|
||||
loc: SourceLocation,
|
||||
loc: SourceLocation
|
||||
): Place {
|
||||
return {
|
||||
kind: 'Identifier',
|
||||
kind: "Identifier",
|
||||
identifier: {
|
||||
id: env.nextIdentifierId,
|
||||
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
|
||||
mutableRange: { start: makeInstructionId(0), end: makeInstructionId(0) },
|
||||
name: null,
|
||||
scope: null,
|
||||
type: makeType(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user