mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Update base for Update on "[compiler] In change detection, assign reactive scopes to values that may have changed between renders"
Summary: Change detection is desgined to determine whether rules of react have been violated, and to do so better we need to loosen Forgets assumptions about what kinds of values don't need to be memoized. For example, the compiler typically doesn't think of o.x as something that needs to be memoized, because it does not allocate. However, we want to compare o.x in the current render with o.x in a previous one, so we now insert a "memoization" (comparison, really) block around this value. The structure of this work is that we now add a reactive scope for identifiers if they originate from any instruction that could read from state that could have mutated between renders. This means that `LoadProperty` is always going to get a reactive scope; `LoadGlobal` will get a scope if we're not reading from something mutable, and `PrefixUpdate` won't (because the variable being incremented would have already been loaded and checked). Supersedes #30180. [ghstack-poisoned]
This commit is contained in:
@@ -48,197 +48,6 @@ parameters:
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
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 --ci=circleci
|
||||
- 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_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=circleci
|
||||
|
||||
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
|
||||
|
||||
publish_prerelease:
|
||||
parameters:
|
||||
commit_sha:
|
||||
@@ -262,78 +71,6 @@ jobs:
|
||||
scripts/release/publish.js --ci --tags << parameters.dist_tag >>
|
||||
|
||||
workflows:
|
||||
|
||||
build_and_test:
|
||||
unless: << pipeline.parameters.prerelease_commit_sha >>
|
||||
jobs:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
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 >>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
c998bb1ed4b3285398c9c7797135d3f060243c6a
|
||||
fd2b3e13d330a4559f5aa21462e1cb2cbbcf144b
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
name: (DevTools) Regression Tests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
prerelease_commit_sha:
|
||||
required: false
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
|
||||
jobs:
|
||||
download_build:
|
||||
if: inputs.prerelease_commit_sha == ''
|
||||
name: Download base build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.20.1
|
||||
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-ghaction.js --commit=$(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: 18.20.1
|
||||
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/circleci/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: 18.20.1
|
||||
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/circleci/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=github
|
||||
|
||||
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: 18.20.1
|
||||
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/circleci/download_devtools_regression_build.js ${{ matrix.version }}
|
||||
- run: ls -R build-regression
|
||||
- run: ./scripts/circleci/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
|
||||
@@ -180,9 +180,8 @@ jobs:
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
|
||||
path: |
|
||||
build
|
||||
name: _build_${{ matrix.worker_id }}_${{ matrix.release_channel }}
|
||||
path: build
|
||||
|
||||
test_build:
|
||||
name: yarn test-build
|
||||
@@ -242,6 +241,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
@@ -269,6 +269,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
@@ -311,6 +312,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
@@ -341,6 +343,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
@@ -370,6 +373,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
@@ -407,6 +411,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
@@ -462,6 +467,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- run: ./scripts/circleci/pack_and_store_devtools_artifacts.sh
|
||||
@@ -507,6 +513,7 @@ jobs:
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- run: |
|
||||
@@ -515,3 +522,88 @@ jobs:
|
||||
- run: ./scripts/circleci/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: 18.20.1
|
||||
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-ghaction.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: 18.20.1
|
||||
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
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
name: (Runtime) Commit Artifacts for Meta WWW and fbsource
|
||||
name: (Runtime) Commit Artifacts for Meta WWW and fbsource V2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, meta-www, meta-fbsource]
|
||||
workflow_run:
|
||||
workflows: ["(Runtime) Build and Test"]
|
||||
types: [completed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
@@ -49,102 +52,27 @@ jobs:
|
||||
run: |
|
||||
echo "www_branch_count=$(git ls-remote --heads origin "refs/heads/meta-www" | wc -l)" >> "$GITHUB_OUTPUT"
|
||||
echo "fbsource_branch_count=$(git ls-remote --heads origin "refs/heads/meta-fbsource" | wc -l)" >> "$GITHUB_OUTPUT"
|
||||
- name: Download and unzip artifacts
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
CIRCLECI_TOKEN: ${{secrets.CIRCLECI_TOKEN_DIFFTRAIN}}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
script: |
|
||||
// TODO: Move this to a script file.
|
||||
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;
|
||||
}
|
||||
node-version: 18.20.1
|
||||
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-ghaction.js --commit=${{ github.event.workflow_run.head_sha }}
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- name: Strip @license from eslint plugin and react-refresh
|
||||
run: |
|
||||
sed -i -e 's/ @license React*//' \
|
||||
@@ -199,9 +127,9 @@ jobs:
|
||||
ls -R ./compiled-rn
|
||||
- name: Add REVISION files
|
||||
run: |
|
||||
echo ${{ github.sha }} >> ./compiled/facebook-www/REVISION
|
||||
echo ${{ github.event.workflow_run.head_sha }} >> ./compiled/facebook-www/REVISION
|
||||
cp ./compiled/facebook-www/REVISION ./compiled/facebook-www/REVISION_TRANSFORMS
|
||||
echo ${{ github.sha }} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
|
||||
echo ${{ github.event.workflow_run.head_sha}} >> ./compiled-rn/facebook-fbsource/xplat/js/react-native-github/Libraries/Renderer/REVISION
|
||||
- name: "Get current version string"
|
||||
id: get_current_version
|
||||
run: |
|
||||
@@ -214,11 +142,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@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: compiled
|
||||
path: compiled/
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: compiled-rn
|
||||
path: compiled-rn/
|
||||
@@ -233,7 +161,7 @@ jobs:
|
||||
ref: builds/facebook-www
|
||||
- name: Ensure clean directory
|
||||
run: rm -rf compiled
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: compiled
|
||||
path: compiled/
|
||||
@@ -298,12 +226,12 @@ jobs:
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.head_commit.message }}
|
||||
${{ github.event.workflow_run.head_commit.message }}
|
||||
|
||||
DiffTrain build for [${{ github.sha }}](https://github.com/facebook/react/commit/${{ github.sha }})
|
||||
DiffTrain build for [${{ github.event.workflow_run.head_sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }})
|
||||
branch: builds/facebook-www
|
||||
commit_user_name: ${{ github.actor }}
|
||||
commit_user_email: ${{ github.actor }}@users.noreply.github.com
|
||||
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) }}
|
||||
create_branch: true
|
||||
|
||||
commit_fbsource_artifacts:
|
||||
@@ -316,7 +244,7 @@ jobs:
|
||||
ref: builds/facebook-fbsource
|
||||
- name: Ensure clean directory
|
||||
run: rm -rf compiled-rn
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: compiled-rn
|
||||
path: compiled-rn/
|
||||
@@ -365,7 +293,7 @@ jobs:
|
||||
git add .
|
||||
- name: Signing files
|
||||
if: steps.check_should_commit.outputs.should_commit == 'true'
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// TODO: Move this to a script file.
|
||||
@@ -456,10 +384,10 @@ jobs:
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: |
|
||||
${{ github.event.head_commit.message }}
|
||||
${{ github.event.workflow_run.head_commit.message }}
|
||||
|
||||
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.sha }}.
|
||||
DiffTrain build for commit https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha }}.
|
||||
branch: builds/facebook-fbsource
|
||||
commit_user_name: ${{ github.actor }}
|
||||
commit_user_email: ${{ github.actor }}@users.noreply.github.com
|
||||
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) }}
|
||||
create_branch: true
|
||||
|
||||
+12
-2
@@ -18,14 +18,24 @@ packages/react-devtools-timeline/static
|
||||
# react compiler
|
||||
compiler/**/dist
|
||||
compiler/**/__tests__/fixtures/**/*.expect.md
|
||||
compiler/**/__tests__/fixtures/**/*.flow.js
|
||||
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/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-inside-logical-expression.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/optional-call-logical.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls-mutable-lambda.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/readonly-object-method-calls.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/tagged-template-in-hook.js
|
||||
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/tagged-template-literal.js
|
||||
|
||||
compiler/crates
|
||||
compiler/apps/playground/public
|
||||
|
||||
compiler/**/LICENSE
|
||||
compiler/.*
|
||||
compiler/*.md*
|
||||
compiler/*.json
|
||||
compiler/*.css
|
||||
|
||||
+4
-21
@@ -1,18 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
compilerPaths,
|
||||
esNextPaths,
|
||||
typescriptPaths,
|
||||
} = require('./scripts/shared/pathsByLanguageVersion');
|
||||
const {esNextPaths} = require('./scripts/shared/pathsByLanguageVersion');
|
||||
|
||||
module.exports = {
|
||||
plugins: ['prettier-plugin-hermes-parser'],
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
bracketSameLine: true,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 80,
|
||||
parser: 'flow',
|
||||
parser: 'hermes',
|
||||
arrowParens: 'avoid',
|
||||
overrides: [
|
||||
{
|
||||
@@ -28,25 +25,11 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: typescriptPaths,
|
||||
files: ['*.ts', '*.tsx'],
|
||||
options: {
|
||||
trailingComma: 'all',
|
||||
parser: 'typescript',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: compilerPaths,
|
||||
options: {
|
||||
requirePragma: false,
|
||||
parser: 'babel-ts',
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
trailingComma: 'es5',
|
||||
bracketSpacing: true,
|
||||
bracketSameLine: false,
|
||||
printWidth: 80,
|
||||
arrowParens: 'always',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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,25 +5,24 @@
|
||||
* 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
|
||||
@@ -45,8 +44,7 @@ const Home: NextPage = () => {
|
||||
<SnackbarProvider
|
||||
preventDuplicate
|
||||
maxSnack={10}
|
||||
Components={{ message: MessageSnackbar }}
|
||||
>
|
||||
Components={{message: MessageSnackbar}}>
|
||||
<Header />
|
||||
<Editor />
|
||||
</SnackbarProvider>
|
||||
|
||||
@@ -5,26 +5,21 @@
|
||||
* 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,8 +17,7 @@ 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,10 @@
|
||||
* 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 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 {
|
||||
CompilerError,
|
||||
CompilerErrorDetail,
|
||||
@@ -20,51 +20,51 @@ 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";
|
||||
} 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") {
|
||||
function parseInput(input: string, language: 'flow' | 'typescript') {
|
||||
// Extract the first line to quickly check for custom test directives
|
||||
if (language === "flow") {
|
||||
if (language === 'flow') {
|
||||
return HermesParser.parse(input, {
|
||||
babel: true,
|
||||
flow: "all",
|
||||
sourceType: "module",
|
||||
flow: 'all',
|
||||
sourceType: 'module',
|
||||
enableExperimentalComponentSyntax: true,
|
||||
});
|
||||
} else {
|
||||
return babelParse(input, {
|
||||
plugins: ["typescript", "jsx"],
|
||||
sourceType: "module",
|
||||
plugins: ['typescript', 'jsx'],
|
||||
sourceType: 'module',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function parseFunctions(
|
||||
source: string,
|
||||
language: "flow" | "typescript"
|
||||
language: 'flow' | 'typescript',
|
||||
): Array<
|
||||
NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
@@ -105,7 +105,7 @@ function parseFunctions(
|
||||
|
||||
const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
[
|
||||
"useFragment",
|
||||
'useFragment',
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -114,7 +114,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
"usePaginationFragment",
|
||||
'usePaginationFragment',
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -123,7 +123,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
"useRefetchableFragment",
|
||||
'useRefetchableFragment',
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -132,7 +132,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
"useLazyLoadQuery",
|
||||
'useLazyLoadQuery',
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -141,7 +141,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
|
||||
},
|
||||
],
|
||||
[
|
||||
"usePreloadedQuery",
|
||||
'usePreloadedQuery',
|
||||
{
|
||||
valueKind: ValueKind.Frozen,
|
||||
effectKind: Effect.Freeze,
|
||||
@@ -156,22 +156,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, 'flow' | 'typescript'] {
|
||||
const results = new Map<string, PrintedCompilerPipelineValue[]>();
|
||||
const error = new CompilerError();
|
||||
const upsert = (result: PrintedCompilerPipelineValue) => {
|
||||
@@ -182,15 +182,15 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
|
||||
results.set(result.name, [result]);
|
||||
}
|
||||
};
|
||||
let language: "flow" | "typescript";
|
||||
let language: 'flow' | 'typescript';
|
||||
if (source.match(/\@flow/)) {
|
||||
language = "flow";
|
||||
language = 'flow';
|
||||
} else {
|
||||
language = "typescript";
|
||||
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)) {
|
||||
@@ -199,16 +199,16 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
|
||||
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 +216,20 @@ function compile(source: string): [CompilerOutput, "flow" | "typescript"] {
|
||||
customHooks: new Map([...COMMON_HOOKS]),
|
||||
},
|
||||
getReactFunctionType(id),
|
||||
"_c",
|
||||
'_c',
|
||||
null,
|
||||
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 +239,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),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "reactive": {
|
||||
case 'reactive': {
|
||||
upsert({
|
||||
kind: "reactive",
|
||||
kind: 'reactive',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: printReactiveFunctionWithOutlined(result.value),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "debug": {
|
||||
case 'debug': {
|
||||
upsert({
|
||||
kind: "debug",
|
||||
kind: 'debug',
|
||||
fnName,
|
||||
name: result.name,
|
||||
value: result.value,
|
||||
@@ -288,24 +288,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}, language];
|
||||
}
|
||||
return [{ kind: "ok", results }, language];
|
||||
return [{kind: 'ok', results}, language];
|
||||
}
|
||||
|
||||
export default function Editor() {
|
||||
const store = useStore();
|
||||
const deferredStore = useDeferredValue(store);
|
||||
const dispatchStore = useStoreDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const {enqueueSnackbar} = useSnackbar();
|
||||
const [compilerOutput, language] = useMemo(
|
||||
() => compile(deferredStore.source),
|
||||
[deferredStore.source]
|
||||
[deferredStore.source],
|
||||
);
|
||||
|
||||
useMountEffect(() => {
|
||||
@@ -313,35 +313,35 @@ 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,28 @@
|
||||
* 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";
|
||||
language: 'flow' | 'typescript';
|
||||
};
|
||||
|
||||
export default function Input({ errors, language }: Props) {
|
||||
export default function Input({errors, language}: Props) {
|
||||
const [monaco, setMonaco] = useState<Monaco | null>(null);
|
||||
const store = useStore();
|
||||
const dispatchStore = useStoreDispatch();
|
||||
@@ -36,11 +36,11 @@ 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 = [
|
||||
@@ -64,11 +64,11 @@ export default function Input({ errors, language }: Props) {
|
||||
8011,
|
||||
8012,
|
||||
8013,
|
||||
...(language === "flow" ? flowDiagnosticDisable : []),
|
||||
...(language === 'flow' ? flowDiagnosticDisable : []),
|
||||
],
|
||||
noSemanticValidation: true,
|
||||
// Monaco can't validate Flow component syntax
|
||||
noSyntaxValidation: language === "flow",
|
||||
noSyntaxValidation: language === 'flow',
|
||||
});
|
||||
}, [monaco, language]);
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function Input({ errors, language }: Props) {
|
||||
if (!value) return;
|
||||
|
||||
dispatchStore({
|
||||
type: "updateFile",
|
||||
type: 'updateFile',
|
||||
payload: {
|
||||
source: value,
|
||||
},
|
||||
@@ -91,11 +91,11 @@ export default function Input({ errors, language }: Props) {
|
||||
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 +106,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,17 +124,16 @@ 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,30 +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"];
|
||||
let nonDiffPasses = ['HIR', 'BuildReactiveFunction', 'EnvironmentConfig'];
|
||||
for (const [passName, text] of concattedResults) {
|
||||
tabs.set(
|
||||
passName,
|
||||
<TextTabContent
|
||||
output={text}
|
||||
diff={lastPassOutput}
|
||||
showInfoPanel={!nonDiffPasses.includes(passName)}
|
||||
></TextTabContent>
|
||||
showInfoPanel={!nonDiffPasses.includes(passName)}></TextTabContent>,
|
||||
);
|
||||
lastPassOutput = text;
|
||||
}
|
||||
@@ -116,25 +115,24 @@ 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"
|
||||
/>
|
||||
</>
|
||||
</>,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -146,23 +144,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 {
|
||||
@@ -173,27 +171,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(['JS', 'HIR']); // Initial and final passes should always be bold
|
||||
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) {
|
||||
@@ -212,18 +210,16 @@ 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>
|
||||
@@ -251,8 +247,7 @@ 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
|
||||
@@ -280,7 +275,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,
|
||||
@@ -294,7 +289,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,8 +60,7 @@ 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>
|
||||
@@ -70,8 +69,7 @@ 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" />
|
||||
) : (
|
||||
@@ -84,8 +82,7 @@ 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,10 +16,9 @@ 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,14 +7,13 @@
|
||||
|
||||
// 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,15 +34,14 @@ 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">
|
||||
@@ -69,16 +68,15 @@ 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,15 +21,14 @@ 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}
|
||||
@@ -73,15 +72,14 @@ 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`}
|
||||
>
|
||||
hasChanged ? 'font-bold' : 'font-light'
|
||||
} text-secondary hover:text-link`}>
|
||||
- {name}
|
||||
</h2>
|
||||
{tabs.get(name) ?? <div>No output for {name}</div>}
|
||||
@@ -91,12 +89,11 @@ 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`}
|
||||
>
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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,24 +14,24 @@ 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 = {
|
||||
@@ -43,7 +43,7 @@ const nextConfig = {
|
||||
return config;
|
||||
},
|
||||
|
||||
transpilePackages: ["monaco-editor"],
|
||||
transpilePackages: ['monaco-editor'],
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"monaco-editor": "^0.34.1",
|
||||
"next": "^13.5.6",
|
||||
"notistack": "^3.0.0-alpha.7",
|
||||
"prettier": "3.0.3",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-format": "^29.3.1",
|
||||
"re-resizable": "^6.9.16",
|
||||
"react": "18.2.0",
|
||||
@@ -42,7 +42,6 @@
|
||||
},
|
||||
"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",
|
||||
|
||||
@@ -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,7 +12,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/facebook/react-forget.git"
|
||||
"url": "git+https://github.com/facebook/react.git"
|
||||
},
|
||||
"scripts": {
|
||||
"copyright": "node scripts/copyright.js",
|
||||
@@ -38,7 +38,8 @@
|
||||
"concurrently": "^7.4.0",
|
||||
"folder-hash": "^4.0.4",
|
||||
"ora": "5.4.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-hermes-parser": "^0.23.0",
|
||||
"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-696af53-20240625",
|
||||
"version": "0.0.0-experimental-334f00b-20240725",
|
||||
"description": "Babel plugin for React Compiler.",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
@@ -63,5 +63,10 @@
|
||||
"@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({
|
||||
|
||||
+46
-46
@@ -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"),
|
||||
t.identifier('Symbol'),
|
||||
t.identifier('for'),
|
||||
false,
|
||||
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;
|
||||
|
||||
+26
-26
@@ -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');
|
||||
}
|
||||
|
||||
+3
-3
@@ -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 --
|
||||
@@ -30,14 +30,14 @@ 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 +49,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 +87,7 @@ module.exports = (useForget: boolean) => {
|
||||
};
|
||||
},
|
||||
],
|
||||
"@babel/plugin-transform-modules-commonjs",
|
||||
'@babel/plugin-transform-modules-commonjs',
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -125,7 +125,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 +140,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 +170,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 +180,11 @@ function ReactForgetFunctionTransform() {
|
||||
const compiled = compile(
|
||||
fn,
|
||||
forgetOptions,
|
||||
"Other",
|
||||
"_c",
|
||||
'Other',
|
||||
'_c',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
compiledFns.add(compiled);
|
||||
|
||||
@@ -193,14 +193,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.
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+1
-1
@@ -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
|
||||
@@ -31,8 +31,8 @@ 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";
|
||||
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
|
||||
process.env['NODE_ENV'] === 'development';
|
||||
if (
|
||||
opts.enableReanimatedCheck === true &&
|
||||
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
|
||||
|
||||
+15
-15
@@ -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,12 +17,12 @@ export function insertGatedFunctionDeclaration(
|
||||
| t.FunctionDeclaration
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression,
|
||||
gating: NonNullable<PluginOptions["gating"]>
|
||||
gating: NonNullable<PluginOptions['gating']>,
|
||||
): void {
|
||||
const gatingExpression = t.conditionalExpression(
|
||||
t.callExpression(t.identifier(gating.importSpecifierName), []),
|
||||
buildFunctionExpression(compiled),
|
||||
buildFunctionExpression(fnPath.node)
|
||||
buildFunctionExpression(fnPath.node),
|
||||
);
|
||||
|
||||
/*
|
||||
@@ -32,30 +32,30 @@ 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", [
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(fnPath.node.id, gatingExpression),
|
||||
])
|
||||
]),
|
||||
);
|
||||
} else if (
|
||||
fnPath.parentPath.node.type === "ExportDefaultDeclaration" &&
|
||||
fnPath.node.type !== "ArrowFunctionExpression" &&
|
||||
fnPath.parentPath.node.type === 'ExportDefaultDeclaration' &&
|
||||
fnPath.node.type !== 'ArrowFunctionExpression' &&
|
||||
fnPath.node.id != null
|
||||
) {
|
||||
fnPath.insertAfter(
|
||||
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name))
|
||||
t.exportDefaultDeclaration(t.identifier(fnPath.node.id.name)),
|
||||
);
|
||||
fnPath.parentPath.replaceWith(
|
||||
t.variableDeclaration("const", [
|
||||
t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
t.identifier(fnPath.node.id.name),
|
||||
gatingExpression
|
||||
gatingExpression,
|
||||
),
|
||||
])
|
||||
]),
|
||||
);
|
||||
} else {
|
||||
fnPath.replaceWith(gatingExpression);
|
||||
@@ -63,16 +63,19 @@ export function insertGatedFunctionDeclaration(
|
||||
}
|
||||
|
||||
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,13 @@ const CompilationModeSchema = z.enum([
|
||||
* false positives, since compilation has a greater impact than linting.
|
||||
* This is the default mode
|
||||
*/
|
||||
"infer",
|
||||
'infer',
|
||||
// Compile only components using Flow component syntax and hooks using hook syntax.
|
||||
"syntax",
|
||||
'syntax',
|
||||
// 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,17 +156,17 @@ 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;
|
||||
@@ -176,7 +176,7 @@ export type LoggerEvent =
|
||||
prunedMemoValues: number;
|
||||
}
|
||||
| {
|
||||
kind: "PipelineError";
|
||||
kind: 'PipelineError';
|
||||
fnLoc: t.SourceLocation | null;
|
||||
data: string;
|
||||
};
|
||||
@@ -186,8 +186,8 @@ export type Logger = {
|
||||
};
|
||||
|
||||
export const defaultOptions: PluginOptions = {
|
||||
compilationMode: "infer",
|
||||
panicThreshold: "none",
|
||||
compilationMode: 'infer',
|
||||
panicThreshold: 'none',
|
||||
environment: {},
|
||||
logger: null,
|
||||
gating: null,
|
||||
@@ -196,19 +196,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 +216,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,10 +5,10 @@
|
||||
* 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,
|
||||
@@ -22,13 +22,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 +36,13 @@ import {
|
||||
inferReactivePlaces,
|
||||
inferReferenceEffects,
|
||||
inlineImmediatelyInvokedFunctionExpressions,
|
||||
} from "../Inference";
|
||||
} from '../Inference';
|
||||
import {
|
||||
constantPropagation,
|
||||
deadCodeElimination,
|
||||
pruneMaybeThrows,
|
||||
} from "../Optimization";
|
||||
import { instructionReordering } from "../Optimization/InstructionReordering";
|
||||
} from '../Optimization';
|
||||
import {instructionReordering} from '../Optimization/InstructionReordering';
|
||||
import {
|
||||
CodegenFunction,
|
||||
alignObjectMethodScopes,
|
||||
@@ -69,23 +69,23 @@ 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 {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';
|
||||
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 +95,15 @@ import {
|
||||
validateNoSetStateInRender,
|
||||
validatePreservedManualMemoization,
|
||||
validateUseMemo,
|
||||
} from "../Validation";
|
||||
import { validateLocalsNotReassignedAfterRender } from "../Validation/ValidateLocalsNotReassignedAfterRender";
|
||||
import { outlineFunctions } from "../Optimization/OutlineFunctions";
|
||||
} from '../Validation';
|
||||
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
|
||||
import {outlineFunctions} from '../Optimization/OutlineFunctions';
|
||||
|
||||
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,7 +114,7 @@ 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(
|
||||
@@ -125,11 +125,11 @@ export function* run(
|
||||
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,48 +144,48 @@ 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);
|
||||
|
||||
if (!env.preserveManualMemo()) {
|
||||
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);
|
||||
@@ -196,27 +196,27 @@ 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 });
|
||||
yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
|
||||
validateLocalsNotReassignedAfterRender(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 });
|
||||
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: 'InstructionReordering', 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);
|
||||
@@ -231,67 +231,67 @@ 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 });
|
||||
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: 'OutlineFunctions', 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,
|
||||
});
|
||||
|
||||
memoizeFbtOperandsInSameScope(hir);
|
||||
const fbtOperands = memoizeFbtOperandsInSameScope(hir);
|
||||
yield log({
|
||||
kind: "hir",
|
||||
name: "MemoizeFbtAndMacroOperandsInSameScope",
|
||||
kind: 'hir',
|
||||
name: 'MemoizeFbtAndMacroOperandsInSameScope',
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -299,15 +299,15 @@ function* runWithEnvironment(
|
||||
|
||||
flattenReactiveLoopsHIR(hir);
|
||||
yield log({
|
||||
kind: "hir",
|
||||
name: "FlattenReactiveLoopsHIR",
|
||||
kind: 'hir',
|
||||
name: 'FlattenReactiveLoopsHIR',
|
||||
value: hir,
|
||||
});
|
||||
|
||||
flattenScopesWithHooksOrUseHIR(hir);
|
||||
yield log({
|
||||
kind: "hir",
|
||||
name: "FlattenScopesWithHooksOrUseHIR",
|
||||
kind: 'hir',
|
||||
name: 'FlattenScopesWithHooksOrUseHIR',
|
||||
value: hir,
|
||||
});
|
||||
assertTerminalSuccessorsExist(hir);
|
||||
@@ -316,8 +316,8 @@ function* runWithEnvironment(
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "BuildReactiveFunction",
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveFunction',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
@@ -325,44 +325,44 @@ 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",
|
||||
kind: 'reactive',
|
||||
name: 'BuildReactiveBlocks',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
flattenReactiveLoops(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "FlattenReactiveLoops",
|
||||
kind: 'reactive',
|
||||
name: 'FlattenReactiveLoops',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
flattenScopesWithHooksOrUse(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "FlattenScopesWithHooks",
|
||||
kind: 'reactive',
|
||||
name: 'FlattenScopesWithHooks',
|
||||
value: reactiveFunction,
|
||||
});
|
||||
}
|
||||
@@ -371,101 +371,101 @@ function* runWithEnvironment(
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -480,10 +480,13 @@ function* runWithEnvironment(
|
||||
validatePreservedManualMemoization(reactiveFunction);
|
||||
}
|
||||
|
||||
const ast = codegenFunction(reactiveFunction, uniqueIdentifiers).unwrap();
|
||||
yield log({ kind: "ast", name: "Codegen", value: ast });
|
||||
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 });
|
||||
yield log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -492,7 +495,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;
|
||||
@@ -507,7 +510,7 @@ export function compileFn(
|
||||
useMemoCacheIdentifier: string,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null
|
||||
code: string | null,
|
||||
): CodegenFunction {
|
||||
let generator = run(
|
||||
func,
|
||||
@@ -516,7 +519,7 @@ export function compileFn(
|
||||
useMemoCacheIdentifier,
|
||||
logger,
|
||||
filename,
|
||||
code
|
||||
code,
|
||||
);
|
||||
while (true) {
|
||||
const next = generator.next();
|
||||
@@ -528,24 +531,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;
|
||||
|
||||
@@ -5,32 +5,32 @@
|
||||
* 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,
|
||||
ErrorSeverity,
|
||||
} from "../CompilerError";
|
||||
} from '../CompilerError';
|
||||
import {
|
||||
ExternalFunction,
|
||||
ReactFunctionType,
|
||||
parseEnvironmentConfig,
|
||||
tryParseExternalFunction,
|
||||
} from "../HIR/Environment";
|
||||
import { CodegenFunction } from "../ReactiveScopes";
|
||||
import { isComponentDeclaration } from "../Utils/ComponentDeclaration";
|
||||
import { isHookDeclaration } from "../Utils/HookDeclaration";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
import { insertGatedFunctionDeclaration } from "./Gating";
|
||||
import { addImportsToProgram, updateMemoCacheFunctionImport } from "./Imports";
|
||||
import { PluginOptions } from "./Options";
|
||||
import { compileFn } from "./Pipeline";
|
||||
} from '../HIR/Environment';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
|
||||
import {isHookDeclaration} from '../Utils/HookDeclaration';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {insertGatedFunctionDeclaration} from './Gating';
|
||||
import {addImportsToProgram, updateMemoCacheFunctionImport} from './Imports';
|
||||
import {PluginOptions} from './Options';
|
||||
import {compileFn} from './Pipeline';
|
||||
import {
|
||||
filterSuppressionsThatAffectFunction,
|
||||
findProgramSuppressions,
|
||||
suppressionsToCompilerError,
|
||||
} from "./Suppression";
|
||||
} from './Suppression';
|
||||
|
||||
export type CompilerPass = {
|
||||
opts: PluginOptions;
|
||||
@@ -40,11 +40,11 @@ export type CompilerPass = {
|
||||
};
|
||||
|
||||
function findDirectiveEnablingMemoization(
|
||||
directives: Array<t.Directive>
|
||||
directives: Array<t.Directive>,
|
||||
): t.Directive | null {
|
||||
for (const directive of directives) {
|
||||
const directiveValue = directive.value.value;
|
||||
if (directiveValue === "use forget" || directiveValue === "use memo") {
|
||||
if (directiveValue === 'use forget' || directiveValue === 'use memo') {
|
||||
return directive;
|
||||
}
|
||||
}
|
||||
@@ -53,13 +53,13 @@ function findDirectiveEnablingMemoization(
|
||||
|
||||
function findDirectiveDisablingMemoization(
|
||||
directives: Array<t.Directive>,
|
||||
options: PluginOptions
|
||||
options: PluginOptions,
|
||||
): t.Directive | null {
|
||||
for (const directive of directives) {
|
||||
const directiveValue = directive.value.value;
|
||||
if (
|
||||
(directiveValue === "use no forget" ||
|
||||
directiveValue === "use no memo") &&
|
||||
(directiveValue === 'use no forget' ||
|
||||
directiveValue === 'use no memo') &&
|
||||
!options.ignoreUseNoForget
|
||||
) {
|
||||
return directive;
|
||||
@@ -75,7 +75,7 @@ function isCriticalError(err: unknown): boolean {
|
||||
function isConfigError(err: unknown): boolean {
|
||||
if (err instanceof CompilerError) {
|
||||
return err.details.some(
|
||||
(detail) => detail.severity === ErrorSeverity.InvalidConfig
|
||||
detail => detail.severity === ErrorSeverity.InvalidConfig,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
@@ -92,7 +92,7 @@ export type CompileResult = {
|
||||
* functions which were outlined. Only original functions need to be gated
|
||||
* if gating mode is enabled.
|
||||
*/
|
||||
kind: "original" | "outlined";
|
||||
kind: 'original' | 'outlined';
|
||||
originalFn: BabelFn;
|
||||
compiledFn: CodegenFunction;
|
||||
};
|
||||
@@ -100,13 +100,13 @@ export type CompileResult = {
|
||||
function handleError(
|
||||
err: unknown,
|
||||
pass: CompilerPass,
|
||||
fnLoc: t.SourceLocation | null
|
||||
fnLoc: t.SourceLocation | null,
|
||||
): void {
|
||||
if (pass.opts.logger) {
|
||||
if (err instanceof CompilerError) {
|
||||
for (const detail of err.details) {
|
||||
pass.opts.logger.logEvent(pass.filename, {
|
||||
kind: "CompileError",
|
||||
kind: 'CompileError',
|
||||
fnLoc,
|
||||
detail: detail.options,
|
||||
});
|
||||
@@ -116,19 +116,19 @@ function handleError(
|
||||
if (err instanceof Error) {
|
||||
stringifiedError = err.stack ?? err.message;
|
||||
} else {
|
||||
stringifiedError = err?.toString() ?? "[ null ]";
|
||||
stringifiedError = err?.toString() ?? '[ null ]';
|
||||
}
|
||||
|
||||
pass.opts.logger.logEvent(pass.filename, {
|
||||
kind: "PipelineError",
|
||||
kind: 'PipelineError',
|
||||
fnLoc,
|
||||
data: stringifiedError,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (
|
||||
pass.opts.panicThreshold === "all_errors" ||
|
||||
(pass.opts.panicThreshold === "critical_errors" && isCriticalError(err)) ||
|
||||
pass.opts.panicThreshold === 'all_errors' ||
|
||||
(pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) ||
|
||||
isConfigError(err) // Always throws regardless of panic threshold
|
||||
) {
|
||||
throw err;
|
||||
@@ -137,16 +137,16 @@ function handleError(
|
||||
|
||||
export function createNewFunctionNode(
|
||||
originalFn: BabelFn,
|
||||
compiledFn: CodegenFunction
|
||||
compiledFn: CodegenFunction,
|
||||
): t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression {
|
||||
let transformedFn:
|
||||
| t.FunctionDeclaration
|
||||
| t.ArrowFunctionExpression
|
||||
| t.FunctionExpression;
|
||||
switch (originalFn.node.type) {
|
||||
case "FunctionDeclaration": {
|
||||
case 'FunctionDeclaration': {
|
||||
const fn: t.FunctionDeclaration = {
|
||||
type: "FunctionDeclaration",
|
||||
type: 'FunctionDeclaration',
|
||||
id: compiledFn.id,
|
||||
loc: originalFn.node.loc ?? null,
|
||||
async: compiledFn.async,
|
||||
@@ -157,9 +157,9 @@ export function createNewFunctionNode(
|
||||
transformedFn = fn;
|
||||
break;
|
||||
}
|
||||
case "ArrowFunctionExpression": {
|
||||
case 'ArrowFunctionExpression': {
|
||||
const fn: t.ArrowFunctionExpression = {
|
||||
type: "ArrowFunctionExpression",
|
||||
type: 'ArrowFunctionExpression',
|
||||
loc: originalFn.node.loc ?? null,
|
||||
async: compiledFn.async,
|
||||
generator: compiledFn.generator,
|
||||
@@ -170,9 +170,9 @@ export function createNewFunctionNode(
|
||||
transformedFn = fn;
|
||||
break;
|
||||
}
|
||||
case "FunctionExpression": {
|
||||
case 'FunctionExpression': {
|
||||
const fn: t.FunctionExpression = {
|
||||
type: "FunctionExpression",
|
||||
type: 'FunctionExpression',
|
||||
id: compiledFn.id,
|
||||
loc: originalFn.node.loc ?? null,
|
||||
async: compiledFn.async,
|
||||
@@ -183,13 +183,66 @@ export function createNewFunctionNode(
|
||||
transformedFn = fn;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
originalFn.node,
|
||||
`Creating unhandled function: ${originalFn.node}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid visiting the new transformed version
|
||||
ALREADY_COMPILED.add(transformedFn);
|
||||
return transformedFn;
|
||||
}
|
||||
|
||||
function insertNewOutlinedFunctionNode(
|
||||
program: NodePath<t.Program>,
|
||||
originalFn: BabelFn,
|
||||
compiledFn: CodegenFunction,
|
||||
): NodePath<t.Function> {
|
||||
switch (originalFn.type) {
|
||||
case 'FunctionDeclaration': {
|
||||
return originalFn.insertAfter(
|
||||
createNewFunctionNode(originalFn, compiledFn),
|
||||
)[0]!;
|
||||
}
|
||||
/**
|
||||
* We can't just append the outlined function as a sibling of the original function if it is an
|
||||
* (Arrow)FunctionExpression parented by a VariableDeclaration, as this would cause its parent
|
||||
* to become a SequenceExpression instead which breaks a bunch of assumptions elsewhere in the
|
||||
* plugin.
|
||||
*
|
||||
* To get around this, we always synthesize a new FunctionDeclaration for the outlined function
|
||||
* and insert it as a true sibling to the original function.
|
||||
*/
|
||||
case 'ArrowFunctionExpression':
|
||||
case 'FunctionExpression': {
|
||||
const fn: t.FunctionDeclaration = {
|
||||
type: 'FunctionDeclaration',
|
||||
id: compiledFn.id,
|
||||
loc: originalFn.node.loc ?? null,
|
||||
async: compiledFn.async,
|
||||
generator: compiledFn.generator,
|
||||
params: compiledFn.params,
|
||||
body: compiledFn.body,
|
||||
};
|
||||
const insertedFuncDecl = program.pushContainer('body', [fn])[0]!;
|
||||
CompilerError.invariant(insertedFuncDecl.isFunctionDeclaration(), {
|
||||
reason: 'Expected inserted function declaration',
|
||||
description: `Got: ${insertedFuncDecl}`,
|
||||
loc: insertedFuncDecl.node?.loc ?? null,
|
||||
});
|
||||
return insertedFuncDecl;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
originalFn,
|
||||
`Inserting unhandled function: ${originalFn}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a hack to work around what seems to be a Babel bug. Babel doesn't
|
||||
* consistently respect the `skip()` function to avoid revisiting a node within
|
||||
@@ -198,15 +251,15 @@ export function createNewFunctionNode(
|
||||
const ALREADY_COMPILED: WeakSet<object> | Set<object> = new (WeakSet ?? Set)();
|
||||
|
||||
const DEFAULT_ESLINT_SUPPRESSIONS = [
|
||||
"react-hooks/exhaustive-deps",
|
||||
"react-hooks/rules-of-hooks",
|
||||
'react-hooks/exhaustive-deps',
|
||||
'react-hooks/rules-of-hooks',
|
||||
];
|
||||
|
||||
function isFilePartOfSources(
|
||||
sources: Array<string> | ((filename: string) => boolean),
|
||||
filename: string
|
||||
filename: string,
|
||||
): boolean {
|
||||
if (typeof sources === "function") {
|
||||
if (typeof sources === 'function') {
|
||||
return sources(filename);
|
||||
}
|
||||
|
||||
@@ -221,7 +274,7 @@ function isFilePartOfSources(
|
||||
|
||||
export function compileProgram(
|
||||
program: NodePath<t.Program>,
|
||||
pass: CompilerPass
|
||||
pass: CompilerPass,
|
||||
): void {
|
||||
if (pass.opts.sources) {
|
||||
if (pass.filename === null) {
|
||||
@@ -233,7 +286,7 @@ export function compileProgram(
|
||||
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
|
||||
severity: ErrorSeverity.InvalidConfig,
|
||||
loc: null,
|
||||
})
|
||||
}),
|
||||
);
|
||||
handleError(error, pass, null);
|
||||
return;
|
||||
@@ -253,8 +306,8 @@ export function compileProgram(
|
||||
}
|
||||
|
||||
const environment = parseEnvironmentConfig(pass.opts.environment ?? {});
|
||||
const useMemoCacheIdentifier = program.scope.generateUidIdentifier("c");
|
||||
const moduleName = pass.opts.runtimeModule ?? "react/compiler-runtime";
|
||||
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
|
||||
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
|
||||
if (hasMemoCacheFunctionImport(program, moduleName)) {
|
||||
return;
|
||||
}
|
||||
@@ -267,12 +320,12 @@ export function compileProgram(
|
||||
const suppressions = findProgramSuppressions(
|
||||
pass.comments,
|
||||
pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS,
|
||||
pass.opts.flowSuppressions
|
||||
pass.opts.flowSuppressions,
|
||||
);
|
||||
const lintError = suppressionsToCompilerError(suppressions);
|
||||
let hasCriticalError = lintError != null;
|
||||
const queue: Array<{
|
||||
kind: "original" | "outlined";
|
||||
kind: 'original' | 'outlined';
|
||||
fn: BabelFn;
|
||||
fnType: ReactFunctionType;
|
||||
}> = [];
|
||||
@@ -292,7 +345,7 @@ export function compileProgram(
|
||||
ALREADY_COMPILED.add(fn.node);
|
||||
fn.skip();
|
||||
|
||||
queue.push({ kind: "original", fn, fnType });
|
||||
queue.push({kind: 'original', fn, fnType});
|
||||
};
|
||||
|
||||
// Main traversal to compile with Forget
|
||||
@@ -324,14 +377,14 @@ export function compileProgram(
|
||||
},
|
||||
{
|
||||
...pass,
|
||||
opts: { ...pass.opts, ...pass.opts },
|
||||
opts: {...pass.opts, ...pass.opts},
|
||||
filename: pass.filename ?? null,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const processFn = (
|
||||
fn: BabelFn,
|
||||
fnType: ReactFunctionType
|
||||
fnType: ReactFunctionType,
|
||||
): null | CodegenFunction => {
|
||||
if (lintError != null) {
|
||||
/**
|
||||
@@ -341,7 +394,7 @@ export function compileProgram(
|
||||
*/
|
||||
const suppressionsInFunction = filterSuppressionsThatAffectFunction(
|
||||
suppressions,
|
||||
fn
|
||||
fn,
|
||||
);
|
||||
if (suppressionsInFunction.length > 0) {
|
||||
handleError(lintError, pass, fn.node.loc ?? null);
|
||||
@@ -357,7 +410,7 @@ export function compileProgram(
|
||||
if (environment.isErr()) {
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason:
|
||||
"Error in validating environment config. This is an advanced setting and not meant to be used directly",
|
||||
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
|
||||
description: environment.unwrapErr().toString(),
|
||||
suggestions: null,
|
||||
loc: null,
|
||||
@@ -372,10 +425,10 @@ export function compileProgram(
|
||||
useMemoCacheIdentifier.name,
|
||||
pass.opts.logger,
|
||||
pass.filename,
|
||||
pass.code
|
||||
pass.code,
|
||||
);
|
||||
pass.opts.logger?.logEvent(pass.filename, {
|
||||
kind: "CompileSuccess",
|
||||
kind: 'CompileSuccess',
|
||||
fnLoc: fn.node.loc ?? null,
|
||||
fnName: compiledFn.id?.name ?? null,
|
||||
memoSlots: compiledFn.memoSlotsUsed,
|
||||
@@ -404,12 +457,14 @@ export function compileProgram(
|
||||
}
|
||||
for (const outlined of compiled.outlined) {
|
||||
CompilerError.invariant(outlined.fn.outlined.length === 0, {
|
||||
reason: "Unexpected nested outlined functions",
|
||||
reason: 'Unexpected nested outlined functions',
|
||||
loc: outlined.fn.loc,
|
||||
});
|
||||
const fn = current.fn.insertAfter(
|
||||
createNewFunctionNode(current.fn, outlined.fn)
|
||||
)[0]!;
|
||||
const fn = insertNewOutlinedFunctionNode(
|
||||
program,
|
||||
current.fn,
|
||||
outlined.fn,
|
||||
);
|
||||
fn.skip();
|
||||
ALREADY_COMPILED.add(fn.node);
|
||||
if (outlined.type !== null) {
|
||||
@@ -437,9 +492,9 @@ export function compileProgram(
|
||||
if (pass.opts.gating != null) {
|
||||
const error = checkFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
program,
|
||||
compiledFns.map((result) => {
|
||||
compiledFns.map(result => {
|
||||
return result.originalFn;
|
||||
})
|
||||
}),
|
||||
);
|
||||
if (error) {
|
||||
handleError(error, pass, null);
|
||||
@@ -460,32 +515,32 @@ export function compileProgram(
|
||||
pass.opts.environment?.enableEmitInstrumentForget;
|
||||
if (enableEmitInstrumentForget != null) {
|
||||
externalFunctions.push(
|
||||
tryParseExternalFunction(enableEmitInstrumentForget.fn)
|
||||
tryParseExternalFunction(enableEmitInstrumentForget.fn),
|
||||
);
|
||||
if (enableEmitInstrumentForget.gating != null) {
|
||||
externalFunctions.push(
|
||||
tryParseExternalFunction(enableEmitInstrumentForget.gating)
|
||||
tryParseExternalFunction(enableEmitInstrumentForget.gating),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (pass.opts.environment?.enableEmitFreeze != null) {
|
||||
const enableEmitFreeze = tryParseExternalFunction(
|
||||
pass.opts.environment.enableEmitFreeze
|
||||
pass.opts.environment.enableEmitFreeze,
|
||||
);
|
||||
externalFunctions.push(enableEmitFreeze);
|
||||
}
|
||||
|
||||
if (pass.opts.environment?.enableEmitHookGuards != null) {
|
||||
const enableEmitHookGuards = tryParseExternalFunction(
|
||||
pass.opts.environment.enableEmitHookGuards
|
||||
pass.opts.environment.enableEmitHookGuards,
|
||||
);
|
||||
externalFunctions.push(enableEmitHookGuards);
|
||||
}
|
||||
|
||||
if (pass.opts.environment?.enableChangeDetectionForDebugging != null) {
|
||||
const enableChangeDetectionForDebugging = tryParseExternalFunction(
|
||||
pass.opts.environment.enableChangeDetectionForDebugging
|
||||
pass.opts.environment.enableChangeDetectionForDebugging,
|
||||
);
|
||||
externalFunctions.push(enableChangeDetectionForDebugging);
|
||||
}
|
||||
@@ -499,10 +554,10 @@ export function compileProgram(
|
||||
* error elsewhere in the file, regardless of bailout mode.
|
||||
*/
|
||||
for (const result of compiledFns) {
|
||||
const { kind, originalFn, compiledFn } = result;
|
||||
const {kind, originalFn, compiledFn} = result;
|
||||
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
|
||||
|
||||
if (gating != null && kind === "original") {
|
||||
if (gating != null && kind === 'original') {
|
||||
insertGatedFunctionDeclaration(originalFn, transformedFn, gating);
|
||||
} else {
|
||||
originalFn.replaceWith(transformedFn);
|
||||
@@ -523,7 +578,7 @@ export function compileProgram(
|
||||
updateMemoCacheFunctionImport(
|
||||
program,
|
||||
moduleName,
|
||||
useMemoCacheIdentifier.name
|
||||
useMemoCacheIdentifier.name,
|
||||
);
|
||||
}
|
||||
addImportsToProgram(program, externalFunctions);
|
||||
@@ -532,18 +587,18 @@ export function compileProgram(
|
||||
|
||||
function getReactFunctionType(
|
||||
fn: BabelFn,
|
||||
pass: CompilerPass
|
||||
pass: CompilerPass,
|
||||
): ReactFunctionType | null {
|
||||
const hookPattern = pass.opts.environment?.hookPattern ?? null;
|
||||
if (fn.node.body.type === "BlockStatement") {
|
||||
if (fn.node.body.type === 'BlockStatement') {
|
||||
// Opt-outs disable compilation regardless of mode
|
||||
const useNoForget = findDirectiveDisablingMemoization(
|
||||
fn.node.body.directives,
|
||||
pass.opts
|
||||
pass.opts,
|
||||
);
|
||||
if (useNoForget != null) {
|
||||
pass.opts.logger?.logEvent(pass.filename, {
|
||||
kind: "CompileError",
|
||||
kind: 'CompileError',
|
||||
fnLoc: fn.node.body.loc ?? null,
|
||||
detail: {
|
||||
severity: ErrorSeverity.Todo,
|
||||
@@ -556,7 +611,7 @@ function getReactFunctionType(
|
||||
}
|
||||
// Otherwise opt-ins enable compilation regardless of mode
|
||||
if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) {
|
||||
return getComponentOrHookLike(fn, hookPattern) ?? "Other";
|
||||
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,36 +619,36 @@ function getReactFunctionType(
|
||||
let componentSyntaxType: ReactFunctionType | null = null;
|
||||
if (fn.isFunctionDeclaration()) {
|
||||
if (isComponentDeclaration(fn.node)) {
|
||||
componentSyntaxType = "Component";
|
||||
componentSyntaxType = 'Component';
|
||||
} else if (isHookDeclaration(fn.node)) {
|
||||
componentSyntaxType = "Hook";
|
||||
componentSyntaxType = 'Hook';
|
||||
}
|
||||
}
|
||||
|
||||
switch (pass.opts.compilationMode) {
|
||||
case "annotation": {
|
||||
case 'annotation': {
|
||||
// opt-ins are checked above
|
||||
return null;
|
||||
}
|
||||
case "infer": {
|
||||
case 'infer': {
|
||||
// Check if this is a component or hook-like function
|
||||
return componentSyntaxType ?? getComponentOrHookLike(fn, hookPattern);
|
||||
}
|
||||
case "syntax": {
|
||||
case 'syntax': {
|
||||
return componentSyntaxType;
|
||||
}
|
||||
case "all": {
|
||||
case 'all': {
|
||||
// Compile only top level functions
|
||||
if (fn.scope.getProgramParent() !== fn.scope.parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getComponentOrHookLike(fn, hookPattern) ?? "Other";
|
||||
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
pass.opts.compilationMode,
|
||||
`Unexpected compilationMode \`${pass.opts.compilationMode}\``
|
||||
`Unexpected compilationMode \`${pass.opts.compilationMode}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -606,12 +661,12 @@ function getReactFunctionType(
|
||||
*/
|
||||
function hasMemoCacheFunctionImport(
|
||||
program: NodePath<t.Program>,
|
||||
moduleName: string
|
||||
moduleName: string,
|
||||
): boolean {
|
||||
let hasUseMemoCache = false;
|
||||
program.traverse({
|
||||
ImportSpecifier(path) {
|
||||
const imported = path.get("imported");
|
||||
const imported = path.get('imported');
|
||||
let importedName: string | null = null;
|
||||
if (imported.isIdentifier()) {
|
||||
importedName = imported.node.name;
|
||||
@@ -619,9 +674,9 @@ function hasMemoCacheFunctionImport(
|
||||
importedName = imported.node.value;
|
||||
}
|
||||
if (
|
||||
importedName === "c" &&
|
||||
importedName === 'c' &&
|
||||
path.parentPath.isImportDeclaration() &&
|
||||
path.parentPath.get("source").node.value === moduleName
|
||||
path.parentPath.get('source').node.value === moduleName
|
||||
) {
|
||||
hasUseMemoCache = true;
|
||||
}
|
||||
@@ -644,18 +699,18 @@ function isHookName(s: string, hookPattern: string | null): boolean {
|
||||
|
||||
function isHook(
|
||||
path: NodePath<t.Expression | t.PrivateName>,
|
||||
hookPattern: string | null
|
||||
hookPattern: string | null,
|
||||
): boolean {
|
||||
if (path.isIdentifier()) {
|
||||
return isHookName(path.node.name, hookPattern);
|
||||
} else if (
|
||||
path.isMemberExpression() &&
|
||||
!path.node.computed &&
|
||||
isHook(path.get("property"), hookPattern)
|
||||
isHook(path.get('property'), hookPattern)
|
||||
) {
|
||||
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;
|
||||
}
|
||||
@@ -672,15 +727,15 @@ function isComponentName(path: NodePath<t.Expression>): boolean {
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
@@ -693,8 +748,8 @@ function isReactAPI(
|
||||
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')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -706,50 +761,50 @@ 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 isValidPropsAnnotation(
|
||||
annot: t.TypeAnnotation | t.TSTypeAnnotation | t.Noop | null | undefined
|
||||
annot: t.TypeAnnotation | t.TSTypeAnnotation | t.Noop | null | undefined,
|
||||
): boolean {
|
||||
if (annot == null) {
|
||||
return true;
|
||||
} else if (annot.type === "TSTypeAnnotation") {
|
||||
} else if (annot.type === 'TSTypeAnnotation') {
|
||||
switch (annot.typeAnnotation.type) {
|
||||
case "TSArrayType":
|
||||
case "TSBigIntKeyword":
|
||||
case "TSBooleanKeyword":
|
||||
case "TSConstructorType":
|
||||
case "TSFunctionType":
|
||||
case "TSLiteralType":
|
||||
case "TSNeverKeyword":
|
||||
case "TSNumberKeyword":
|
||||
case "TSStringKeyword":
|
||||
case "TSSymbolKeyword":
|
||||
case "TSTupleType":
|
||||
case 'TSArrayType':
|
||||
case 'TSBigIntKeyword':
|
||||
case 'TSBooleanKeyword':
|
||||
case 'TSConstructorType':
|
||||
case 'TSFunctionType':
|
||||
case 'TSLiteralType':
|
||||
case 'TSNeverKeyword':
|
||||
case 'TSNumberKeyword':
|
||||
case 'TSStringKeyword':
|
||||
case 'TSSymbolKeyword':
|
||||
case 'TSTupleType':
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (annot.type === "TypeAnnotation") {
|
||||
} else if (annot.type === 'TypeAnnotation') {
|
||||
switch (annot.typeAnnotation.type) {
|
||||
case "ArrayTypeAnnotation":
|
||||
case "BooleanLiteralTypeAnnotation":
|
||||
case "BooleanTypeAnnotation":
|
||||
case "EmptyTypeAnnotation":
|
||||
case "FunctionTypeAnnotation":
|
||||
case "NumberLiteralTypeAnnotation":
|
||||
case "NumberTypeAnnotation":
|
||||
case "StringLiteralTypeAnnotation":
|
||||
case "StringTypeAnnotation":
|
||||
case "SymbolTypeAnnotation":
|
||||
case "ThisTypeAnnotation":
|
||||
case "TupleTypeAnnotation":
|
||||
case 'ArrayTypeAnnotation':
|
||||
case 'BooleanLiteralTypeAnnotation':
|
||||
case 'BooleanTypeAnnotation':
|
||||
case 'EmptyTypeAnnotation':
|
||||
case 'FunctionTypeAnnotation':
|
||||
case 'NumberLiteralTypeAnnotation':
|
||||
case 'NumberTypeAnnotation':
|
||||
case 'StringLiteralTypeAnnotation':
|
||||
case 'StringTypeAnnotation':
|
||||
case 'SymbolTypeAnnotation':
|
||||
case 'ThisTypeAnnotation':
|
||||
case 'TupleTypeAnnotation':
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (annot.type === "Noop") {
|
||||
} else if (annot.type === 'Noop') {
|
||||
return true;
|
||||
} else {
|
||||
assertExhaustive(annot, `Unexpected annotation node \`${annot}\``);
|
||||
@@ -757,7 +812,7 @@ function isValidPropsAnnotation(
|
||||
}
|
||||
|
||||
function isValidComponentParams(
|
||||
params: Array<NodePath<t.Identifier | t.Pattern | t.RestElement>>
|
||||
params: Array<NodePath<t.Identifier | t.Pattern | t.RestElement>>,
|
||||
): boolean {
|
||||
if (params.length === 0) {
|
||||
return true;
|
||||
@@ -770,8 +825,8 @@ function isValidComponentParams(
|
||||
return !params[0].isRestElement();
|
||||
} else if (params[1].isIdentifier()) {
|
||||
// check if second param might be a ref
|
||||
const { name } = params[1].node;
|
||||
return name.includes("ref") || name.includes("Ref");
|
||||
const {name} = params[1].node;
|
||||
return name.includes('ref') || name.includes('Ref');
|
||||
} else {
|
||||
/**
|
||||
* Otherwise, avoid helper functions that take more than one argument.
|
||||
@@ -792,19 +847,19 @@ function getComponentOrHookLike(
|
||||
node: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
hookPattern: string | null
|
||||
hookPattern: string | null,
|
||||
): ReactFunctionType | null {
|
||||
const functionName = getFunctionName(node);
|
||||
// Check if the name is component or hook like:
|
||||
if (functionName !== null && isComponentName(functionName)) {
|
||||
let isComponent =
|
||||
callsHooksOrCreatesJsx(node, hookPattern) &&
|
||||
isValidComponentParams(node.get("params")) &&
|
||||
isValidComponentParams(node.get('params')) &&
|
||||
!returnsNonNode(node);
|
||||
return isComponent ? "Component" : null;
|
||||
return isComponent ? 'Component' : null;
|
||||
} else if (functionName !== null && isHook(functionName, hookPattern)) {
|
||||
// Hooks have hook invocations or JSX, but can take any # of arguments
|
||||
return callsHooksOrCreatesJsx(node, hookPattern) ? "Hook" : null;
|
||||
return callsHooksOrCreatesJsx(node, hookPattern) ? 'Hook' : null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -814,7 +869,7 @@ function getComponentOrHookLike(
|
||||
if (node.isFunctionExpression() || node.isArrowFunctionExpression()) {
|
||||
if (isForwardRefCallback(node) || isMemoCallback(node)) {
|
||||
// As an added check we also look for hook invocations or JSX
|
||||
return callsHooksOrCreatesJsx(node, hookPattern) ? "Component" : null;
|
||||
return callsHooksOrCreatesJsx(node, hookPattern) ? 'Component' : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -823,12 +878,12 @@ function getComponentOrHookLike(
|
||||
function skipNestedFunctions(
|
||||
node: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>
|
||||
>,
|
||||
) {
|
||||
return (
|
||||
fn: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>
|
||||
>,
|
||||
): void => {
|
||||
if (fn.node !== node.node) {
|
||||
fn.skip();
|
||||
@@ -840,7 +895,7 @@ function callsHooksOrCreatesJsx(
|
||||
node: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>,
|
||||
hookPattern: string | null
|
||||
hookPattern: string | null,
|
||||
): boolean {
|
||||
let invokesHooks = false;
|
||||
let createsJsx = false;
|
||||
@@ -850,7 +905,7 @@ function callsHooksOrCreatesJsx(
|
||||
createsJsx = true;
|
||||
},
|
||||
CallExpression(call) {
|
||||
const callee = call.get("callee");
|
||||
const callee = call.get('callee');
|
||||
if (callee.isExpression() && isHook(callee, hookPattern)) {
|
||||
invokesHooks = true;
|
||||
}
|
||||
@@ -866,7 +921,7 @@ function callsHooksOrCreatesJsx(
|
||||
function returnsNonNode(
|
||||
node: NodePath<
|
||||
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
|
||||
>
|
||||
>,
|
||||
): boolean {
|
||||
let hasReturn = false;
|
||||
let returnsNonNode = false;
|
||||
@@ -879,12 +934,12 @@ function returnsNonNode(
|
||||
returnsNonNode = true;
|
||||
} else {
|
||||
switch (argument.type) {
|
||||
case "ObjectExpression":
|
||||
case "ArrowFunctionExpression":
|
||||
case "FunctionExpression":
|
||||
case "BigIntLiteral":
|
||||
case "ClassExpression":
|
||||
case "NewExpression": // technically `new Array()` is legit, but unlikely
|
||||
case 'ObjectExpression':
|
||||
case 'ArrowFunctionExpression':
|
||||
case 'FunctionExpression':
|
||||
case 'BigIntLiteral':
|
||||
case 'ClassExpression':
|
||||
case 'NewExpression': // technically `new Array()` is legit, but unlikely
|
||||
returnsNonNode = true;
|
||||
}
|
||||
}
|
||||
@@ -908,10 +963,10 @@ function returnsNonNode(
|
||||
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;
|
||||
}
|
||||
@@ -919,31 +974,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 = () => {}} = {};
|
||||
@@ -952,7 +1007,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;
|
||||
@@ -963,17 +1018,17 @@ function getFunctionName(
|
||||
|
||||
function checkFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
program: NodePath<t.Program>,
|
||||
fns: Array<BabelFn>
|
||||
fns: Array<BabelFn>,
|
||||
): CompilerError | null {
|
||||
const fnIds = new Set(
|
||||
fns
|
||||
.map((fn) => getFunctionName(fn))
|
||||
.map(fn => getFunctionName(fn))
|
||||
.filter(
|
||||
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier()
|
||||
(name): name is NodePath<t.Identifier> => !!name && name.isIdentifier(),
|
||||
)
|
||||
.map((name) => name.node)
|
||||
.map(name => name.node),
|
||||
);
|
||||
const fnNames = new Map([...fnIds].map((id) => [id.name, id]));
|
||||
const fnNames = new Map([...fnIds].map(id => [id.name, id]));
|
||||
const errors = new CompilerError();
|
||||
|
||||
program.traverse({
|
||||
@@ -1019,7 +1074,7 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
loc: fn.loc ?? null,
|
||||
suggestions: null,
|
||||
severity: ErrorSeverity.Invariant,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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';
|
||||
|
||||
+6
-6
@@ -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) {
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
* 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";
|
||||
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) => {
|
||||
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
|
||||
block.terminal,
|
||||
)}'`,
|
||||
loc: (block.terminal as any).loc ?? GeneratedSource,
|
||||
suggestions: null,
|
||||
@@ -31,16 +31,17 @@ export function assertTerminalPredsExist(fn: HIRFunction): void {
|
||||
for (const pred of block.preds) {
|
||||
const predBlock = fn.body.blocks.get(pred);
|
||||
CompilerError.invariant(predBlock != null, {
|
||||
reason: "Expected predecessor block to exist",
|
||||
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",
|
||||
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,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
+54
-26
@@ -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,7 +12,12 @@ import {
|
||||
ReactiveScope,
|
||||
ReactiveScopeTerminal,
|
||||
ScopeId,
|
||||
} from "./HIR";
|
||||
} from './HIR';
|
||||
import {
|
||||
markInstructionIds,
|
||||
markPredecessors,
|
||||
reversePostorderBlocks,
|
||||
} from './HIRBuilder';
|
||||
|
||||
/**
|
||||
* This pass assumes that all program blocks are properly nested with respect to fallthroughs
|
||||
@@ -72,14 +77,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,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -142,16 +147,9 @@ export function buildReactiveScopeTerminalsHIR(fn: HIRFunction): void {
|
||||
|
||||
/**
|
||||
* Step 3:
|
||||
* Repoint preds and phis when they refer to a rewritten block.
|
||||
* Repoint 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);
|
||||
@@ -162,18 +160,48 @@ 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;
|
||||
};
|
||||
@@ -190,12 +218,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,
|
||||
@@ -206,15 +234,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,
|
||||
});
|
||||
@@ -248,13 +276,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,
|
||||
@@ -262,7 +290,7 @@ function handleRewrite(
|
||||
loc: GeneratedSource,
|
||||
}
|
||||
: {
|
||||
kind: "goto",
|
||||
kind: 'goto',
|
||||
variant: GotoVariant.Break,
|
||||
block: terminalInfo.fallthroughId,
|
||||
id: terminalInfo.instrId,
|
||||
@@ -281,7 +309,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,19 +5,19 @@
|
||||
* 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,
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
makeIdentifierId,
|
||||
makeIdentifierName,
|
||||
makeScopeId,
|
||||
} from "./HIR";
|
||||
} from './HIR';
|
||||
import {
|
||||
BuiltInMixedReadonlyId,
|
||||
DefaultMutatingHook,
|
||||
@@ -43,8 +43,8 @@ import {
|
||||
FunctionSignature,
|
||||
ShapeRegistry,
|
||||
addHook,
|
||||
} from "./ObjectShape";
|
||||
import { Scope as BabelScope } from "@babel/traverse";
|
||||
} from './ObjectShape';
|
||||
import {Scope as BabelScope} from '@babel/traverse';
|
||||
|
||||
export const ExternalFunctionSchema = z.object({
|
||||
// Source for the imported module that exports the `importSpecifierName` functions
|
||||
@@ -61,8 +61,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>;
|
||||
@@ -443,34 +443,34 @@ 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")
|
||||
key === 'enableChangeDetectionForDebugging' &&
|
||||
(val === undefined || val === 'true')
|
||||
) {
|
||||
maybeConfig[key] = {
|
||||
source: "react-compiler-runtime",
|
||||
importSpecifierName: "$structuralCheck",
|
||||
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 +483,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 +492,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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -537,7 +537,7 @@ export class Environment {
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
useMemoCacheIdentifier: string
|
||||
useMemoCacheIdentifier: string,
|
||||
) {
|
||||
this.#scope = scope;
|
||||
this.fnType = fnType;
|
||||
@@ -574,13 +574,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,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -613,14 +613,14 @@ export class Environment {
|
||||
}
|
||||
|
||||
generateGloballyUniqueIdentifierName(
|
||||
name: string | null
|
||||
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 });
|
||||
this.#outlinedFunctions.push({fn, type});
|
||||
}
|
||||
|
||||
getOutlinedFunctions(): Array<{
|
||||
@@ -635,7 +635,7 @@ export class Environment {
|
||||
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 +644,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 +681,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 +698,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 +726,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 +739,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, {
|
||||
@@ -781,7 +781,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) {
|
||||
@@ -792,7 +792,7 @@ export function parseEnvironmentConfig(
|
||||
}
|
||||
|
||||
export function validateEnvironmentConfig(
|
||||
partialConfig: PartialEnvironmentConfig
|
||||
partialConfig: PartialEnvironmentConfig,
|
||||
): EnvironmentConfig {
|
||||
const config = EnvironmentConfigSchema.safeParse(partialConfig);
|
||||
if (config.success) {
|
||||
@@ -801,7 +801,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,
|
||||
@@ -809,10 +809,10 @@ export function validateEnvironmentConfig(
|
||||
}
|
||||
|
||||
export function tryParseExternalFunction(
|
||||
maybeExternalFunction: any
|
||||
maybeExternalFunction: any,
|
||||
): ExternalFunction {
|
||||
const externalFunction = ExternalFunctionSchema.safeParse(
|
||||
maybeExternalFunction
|
||||
maybeExternalFunction,
|
||||
);
|
||||
if (externalFunction.success) {
|
||||
return externalFunction.data;
|
||||
@@ -820,7 +820,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,7 +5,7 @@
|
||||
* 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,
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
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 +37,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 +106,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 +119,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 +205,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 +244,179 @@ 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",
|
||||
'useActionState',
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: { kind: "Object", shapeId: BuiltInUseActionStateId },
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseActionStateId},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: "useActionState",
|
||||
hookKind: 'useActionState',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"useReducer",
|
||||
'useReducer',
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: { kind: "Object", shapeId: BuiltInUseReducerId },
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseReducerId},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: "useReducer",
|
||||
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: 'useInsertionEffect',
|
||||
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 +424,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 +442,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 +452,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 +475,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 +488,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 +520,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 {
|
||||
@@ -131,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 {
|
||||
@@ -153,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -180,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,
|
||||
@@ -188,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);
|
||||
@@ -229,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
|
||||
@@ -246,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,
|
||||
};
|
||||
@@ -254,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,
|
||||
};
|
||||
@@ -266,7 +266,7 @@ export default class HIRBuilder {
|
||||
const importDeclaration =
|
||||
path.parentPath as NodePath<t.ImportDeclaration>;
|
||||
return {
|
||||
kind: "ImportNamespace",
|
||||
kind: 'ImportNamespace',
|
||||
name: originalName,
|
||||
module: importDeclaration.node.source.value,
|
||||
};
|
||||
@@ -276,7 +276,7 @@ export default class HIRBuilder {
|
||||
path.isClassDeclaration() ||
|
||||
path.isClassExpression();
|
||||
return {
|
||||
kind: "ModuleLocal",
|
||||
kind: 'ModuleLocal',
|
||||
name: originalName,
|
||||
immutable,
|
||||
};
|
||||
@@ -288,7 +288,7 @@ export default class HIRBuilder {
|
||||
babelBinding.scope.rename(originalName, resolvedBinding.name.value);
|
||||
}
|
||||
return {
|
||||
kind: "Identifier",
|
||||
kind: 'Identifier',
|
||||
identifier: resolvedBinding,
|
||||
bindingKind: babelBinding.kind,
|
||||
};
|
||||
@@ -299,7 +299,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;
|
||||
@@ -329,7 +329,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;
|
||||
@@ -350,7 +350,7 @@ export default class HIRBuilder {
|
||||
if (
|
||||
!rpoBlocks.has(id) &&
|
||||
block.instructions.some(
|
||||
(instr) => instr.value.kind === "FunctionExpression"
|
||||
instr => instr.value.kind === 'FunctionExpression',
|
||||
)
|
||||
) {
|
||||
CompilerError.throwTodo({
|
||||
@@ -374,7 +374,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,
|
||||
@@ -394,7 +394,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,
|
||||
@@ -417,7 +417,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,
|
||||
@@ -436,7 +436,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,
|
||||
@@ -464,7 +464,7 @@ export default class HIRBuilder {
|
||||
|
||||
label<T>(label: string, breakBlock: BlockId, fn: () => T): T {
|
||||
this.#scopes.push({
|
||||
kind: "label",
|
||||
kind: 'label',
|
||||
breakBlock,
|
||||
label,
|
||||
});
|
||||
@@ -472,22 +472,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,
|
||||
});
|
||||
@@ -495,15 +495,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;
|
||||
}
|
||||
@@ -518,10 +518,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,
|
||||
@@ -530,16 +530,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;
|
||||
}
|
||||
@@ -553,14 +553,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,
|
||||
@@ -575,13 +575,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,
|
||||
@@ -589,7 +589,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,
|
||||
@@ -638,7 +638,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;
|
||||
@@ -654,7 +654,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)
|
||||
) {
|
||||
@@ -675,10 +675,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,
|
||||
@@ -705,7 +705,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();
|
||||
@@ -777,7 +777,7 @@ function getReversePostorderedBlocks(func: HIR): HIR["blocks"] {
|
||||
...block,
|
||||
instructions: [],
|
||||
terminal: {
|
||||
kind: "unreachable",
|
||||
kind: 'unreachable',
|
||||
id: block.terminal.id,
|
||||
loc: block.terminal.loc,
|
||||
},
|
||||
@@ -818,7 +818,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,
|
||||
});
|
||||
@@ -831,7 +831,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);
|
||||
@@ -846,7 +846,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;
|
||||
@@ -859,14 +859,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,
|
||||
@@ -887,13 +887,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(),
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
BlockId,
|
||||
Effect,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Instruction,
|
||||
} from "./HIR";
|
||||
import { markPredecessors } from "./HIRBuilder";
|
||||
import { terminalFallthrough, terminalHasFallthrough } from "./visitors";
|
||||
} from './HIR';
|
||||
import {markPredecessors} from './HIRBuilder';
|
||||
import {terminalFallthrough, terminalHasFallthrough} from './visitors';
|
||||
|
||||
/*
|
||||
* Merges sequences of blocks that will always execute consecutively —
|
||||
@@ -39,8 +39,8 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === "FunctionExpression" ||
|
||||
instr.value.kind === "ObjectMethod"
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
mergeConsecutiveBlocks(instr.value.loweredFunc.func);
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
// Can only merge blocks with a single predecessor
|
||||
block.preds.size !== 1 ||
|
||||
// Value blocks cannot merge
|
||||
block.kind !== "block" ||
|
||||
block.kind !== 'block' ||
|
||||
// Merging across fallthroughs could move the predecessor out of its block scope
|
||||
fallthroughBlocks.has(block.id)
|
||||
) {
|
||||
@@ -65,7 +65,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
if (predecessor.terminal.kind !== "goto" || predecessor.kind !== "block") {
|
||||
if (predecessor.terminal.kind !== 'goto' || predecessor.kind !== 'block') {
|
||||
/*
|
||||
* The predecessor is not guaranteed to transfer control to this block,
|
||||
* they aren't consecutive.
|
||||
@@ -85,16 +85,16 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
const instr: Instruction = {
|
||||
id: predecessor.terminal.id,
|
||||
lvalue: {
|
||||
kind: "Identifier",
|
||||
kind: 'Identifier',
|
||||
identifier: phi.id,
|
||||
effect: Effect.ConditionallyMutate,
|
||||
reactive: false,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
value: {
|
||||
kind: "LoadLocal",
|
||||
kind: 'LoadLocal',
|
||||
place: {
|
||||
kind: "Identifier",
|
||||
kind: 'Identifier',
|
||||
identifier: operand,
|
||||
effect: Effect.Read,
|
||||
reactive: false,
|
||||
@@ -113,7 +113,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
fn.body.blocks.delete(block.id);
|
||||
}
|
||||
markPredecessors(fn.body);
|
||||
for (const [, { terminal }] of fn.body.blocks) {
|
||||
for (const [, {terminal}] of fn.body.blocks) {
|
||||
if (terminalHasFallthrough(terminal)) {
|
||||
terminal.fallthrough = merged.get(terminal.fallthrough);
|
||||
}
|
||||
|
||||
+20
-20
@@ -4,16 +4,16 @@ import {
|
||||
Place,
|
||||
ReactiveScope,
|
||||
makeInstructionId,
|
||||
} from ".";
|
||||
import { getPlaceScope } from "../ReactiveScopes/BuildReactiveBlocks";
|
||||
import { isMutable } from "../ReactiveScopes/InferReactiveScopeVariables";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { getOrInsertDefault } from "../Utils/utils";
|
||||
} from '.';
|
||||
import {getPlaceScope} from '../ReactiveScopes/BuildReactiveBlocks';
|
||||
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {getOrInsertDefault} from '../Utils/utils';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionOperand,
|
||||
eachTerminalOperand,
|
||||
} from "./visitors";
|
||||
} from './visitors';
|
||||
|
||||
/**
|
||||
* While previous passes ensure that reactive scopes span valid sets of program
|
||||
@@ -113,10 +113,10 @@ export function mergeOverlappingReactiveScopesHIR(fn: HIRFunction): void {
|
||||
joinedScopes.forEach((scope, groupScope) => {
|
||||
if (scope !== groupScope) {
|
||||
groupScope.range.start = makeInstructionId(
|
||||
Math.min(groupScope.range.start, scope.range.start)
|
||||
Math.min(groupScope.range.start, scope.range.start),
|
||||
);
|
||||
groupScope.range.end = makeInstructionId(
|
||||
Math.max(groupScope.range.end, scope.range.end)
|
||||
Math.max(groupScope.range.end, scope.range.end),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -129,8 +129,8 @@ export function mergeOverlappingReactiveScopesHIR(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
type ScopeInfo = {
|
||||
scopeStarts: Array<{ id: InstructionId; scopes: Set<ReactiveScope> }>;
|
||||
scopeEnds: Array<{ id: InstructionId; scopes: Set<ReactiveScope> }>;
|
||||
scopeStarts: Array<{id: InstructionId; scopes: Set<ReactiveScope>}>;
|
||||
scopeEnds: Array<{id: InstructionId; scopes: Set<ReactiveScope>}>;
|
||||
placeScopes: Map<Place, ReactiveScope>;
|
||||
};
|
||||
|
||||
@@ -150,7 +150,7 @@ function collectScopeInfo(fn: HIRFunction): ScopeInfo {
|
||||
placeScopes.set(place, scope);
|
||||
if (scope.range.start !== scope.range.end) {
|
||||
getOrInsertDefault(scopeStarts, scope.range.start, new Set()).add(
|
||||
scope
|
||||
scope,
|
||||
);
|
||||
getOrInsertDefault(scopeEnds, scope.range.end, new Set()).add(scope);
|
||||
}
|
||||
@@ -173,10 +173,10 @@ function collectScopeInfo(fn: HIRFunction): ScopeInfo {
|
||||
|
||||
return {
|
||||
scopeStarts: [...scopeStarts.entries()]
|
||||
.map(([id, scopes]) => ({ id, scopes }))
|
||||
.map(([id, scopes]) => ({id, scopes}))
|
||||
.sort((a, b) => b.id - a.id),
|
||||
scopeEnds: [...scopeEnds.entries()]
|
||||
.map(([id, scopes]) => ({ id, scopes }))
|
||||
.map(([id, scopes]) => ({id, scopes}))
|
||||
.sort((a, b) => b.id - a.id),
|
||||
placeScopes,
|
||||
};
|
||||
@@ -184,8 +184,8 @@ function collectScopeInfo(fn: HIRFunction): ScopeInfo {
|
||||
|
||||
function visitInstructionId(
|
||||
id: InstructionId,
|
||||
{ scopeEnds, scopeStarts }: ScopeInfo,
|
||||
{ activeScopes, joined }: TraversalState
|
||||
{scopeEnds, scopeStarts}: ScopeInfo,
|
||||
{activeScopes, joined}: TraversalState,
|
||||
): void {
|
||||
/**
|
||||
* Handle all scopes that end at this instruction.
|
||||
@@ -200,7 +200,7 @@ function visitInstructionId(
|
||||
* order of start IDs because the scopes stack is ordered as such
|
||||
*/
|
||||
const scopesSortedStartDescending = [...scopeEndTop.scopes].sort(
|
||||
(a, b) => b.range.start - a.range.start
|
||||
(a, b) => b.range.start - a.range.start,
|
||||
);
|
||||
for (const scope of scopesSortedStartDescending) {
|
||||
const idx = activeScopes.indexOf(scope);
|
||||
@@ -227,7 +227,7 @@ function visitInstructionId(
|
||||
scopeStarts.pop();
|
||||
|
||||
const scopesSortedEndDescending = [...scopeStartTop.scopes].sort(
|
||||
(a, b) => b.range.end - a.range.end
|
||||
(a, b) => b.range.end - a.range.end,
|
||||
);
|
||||
activeScopes.push(...scopesSortedEndDescending);
|
||||
/**
|
||||
@@ -247,14 +247,14 @@ function visitInstructionId(
|
||||
function visitPlace(
|
||||
id: InstructionId,
|
||||
place: Place,
|
||||
{ activeScopes, joined }: TraversalState
|
||||
{activeScopes, joined}: TraversalState,
|
||||
): void {
|
||||
/**
|
||||
* If an instruction mutates an outer scope, flatten all scopes from the top
|
||||
* of the stack to the mutated outer scope.
|
||||
*/
|
||||
const placeScope = getPlaceScope(id, place);
|
||||
if (placeScope != null && isMutable({ id } as any, place)) {
|
||||
if (placeScope != null && isMutable({id} as any, place)) {
|
||||
const placeScopeIdx = activeScopes.indexOf(placeScope);
|
||||
if (placeScopeIdx !== -1 && placeScopeIdx !== activeScopes.length - 1) {
|
||||
joined.union([placeScope, ...activeScopes.slice(placeScopeIdx + 1)]);
|
||||
@@ -264,7 +264,7 @@ function visitPlace(
|
||||
|
||||
function getOverlappingReactiveScopes(
|
||||
fn: HIRFunction,
|
||||
context: ScopeInfo
|
||||
context: ScopeInfo,
|
||||
): DisjointSet<ReactiveScope> {
|
||||
const state: TraversalState = {
|
||||
joined: new DisjointSet<ReactiveScope>(),
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import { Effect, ValueKind, ValueReason } from "./HIR";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
BuiltInType,
|
||||
FunctionType,
|
||||
ObjectType,
|
||||
PolyType,
|
||||
PrimitiveType,
|
||||
} from "./Types";
|
||||
} from './Types';
|
||||
|
||||
/*
|
||||
* This file exports types and defaults for JavaScript object shapes. These are
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
*/
|
||||
|
||||
const PRIMITIVE_TYPE: PrimitiveType = {
|
||||
kind: "Primitive",
|
||||
kind: 'Primitive',
|
||||
};
|
||||
|
||||
let nextAnonId = 0;
|
||||
@@ -42,8 +42,8 @@ function createAnonId(): string {
|
||||
export function addFunction(
|
||||
registry: ShapeRegistry,
|
||||
properties: Iterable<[string, BuiltInType | PolyType]>,
|
||||
fn: Omit<FunctionSignature, "hookKind">,
|
||||
id: string | null = null
|
||||
fn: Omit<FunctionSignature, 'hookKind'>,
|
||||
id: string | null = null,
|
||||
): FunctionType {
|
||||
const shapeId = id ?? createAnonId();
|
||||
addShape(registry, shapeId, properties, {
|
||||
@@ -51,7 +51,7 @@ export function addFunction(
|
||||
hookKind: null,
|
||||
});
|
||||
return {
|
||||
kind: "Function",
|
||||
kind: 'Function',
|
||||
return: fn.returnType,
|
||||
shapeId,
|
||||
};
|
||||
@@ -64,13 +64,13 @@ export function addFunction(
|
||||
*/
|
||||
export function addHook(
|
||||
registry: ShapeRegistry,
|
||||
fn: FunctionSignature & { hookKind: HookKind },
|
||||
id: string | null = null
|
||||
fn: FunctionSignature & {hookKind: HookKind},
|
||||
id: string | null = null,
|
||||
): FunctionType {
|
||||
const shapeId = id ?? createAnonId();
|
||||
addShape(registry, shapeId, [], fn);
|
||||
return {
|
||||
kind: "Function",
|
||||
kind: 'Function',
|
||||
return: fn.returnType,
|
||||
shapeId,
|
||||
};
|
||||
@@ -84,12 +84,12 @@ export function addHook(
|
||||
export function addObject(
|
||||
registry: ShapeRegistry,
|
||||
id: string | null,
|
||||
properties: Iterable<[string, BuiltInType | PolyType]>
|
||||
properties: Iterable<[string, BuiltInType | PolyType]>,
|
||||
): ObjectType {
|
||||
const shapeId = id ?? createAnonId();
|
||||
addShape(registry, shapeId, properties, null);
|
||||
return {
|
||||
kind: "Object",
|
||||
kind: 'Object',
|
||||
shapeId,
|
||||
};
|
||||
}
|
||||
@@ -98,7 +98,7 @@ function addShape(
|
||||
registry: ShapeRegistry,
|
||||
id: string,
|
||||
properties: Iterable<[string, BuiltInType | PolyType]>,
|
||||
functionType: FunctionSignature | null
|
||||
functionType: FunctionSignature | null,
|
||||
): ObjectShape {
|
||||
const shape: ObjectShape = {
|
||||
properties: new Map(properties),
|
||||
@@ -116,17 +116,17 @@ function addShape(
|
||||
}
|
||||
|
||||
export type HookKind =
|
||||
| "useContext"
|
||||
| "useState"
|
||||
| "useActionState"
|
||||
| "useReducer"
|
||||
| "useRef"
|
||||
| "useEffect"
|
||||
| "useLayoutEffect"
|
||||
| "useInsertionEffect"
|
||||
| "useMemo"
|
||||
| "useCallback"
|
||||
| "Custom";
|
||||
| 'useContext'
|
||||
| 'useState'
|
||||
| 'useActionState'
|
||||
| 'useReducer'
|
||||
| 'useRef'
|
||||
| 'useEffect'
|
||||
| 'useLayoutEffect'
|
||||
| 'useInsertionEffect'
|
||||
| 'useMemo'
|
||||
| 'useCallback'
|
||||
| 'Custom';
|
||||
|
||||
/*
|
||||
* Call signature of a function, used for type and effect inference.
|
||||
@@ -190,91 +190,91 @@ export type ObjectShape = {
|
||||
* the inferred types for [] and {}.
|
||||
*/
|
||||
export type ShapeRegistry = Map<string, ObjectShape>;
|
||||
export const BuiltInPropsId = "BuiltInProps";
|
||||
export const BuiltInArrayId = "BuiltInArray";
|
||||
export const BuiltInFunctionId = "BuiltInFunction";
|
||||
export const BuiltInJsxId = "BuiltInJsx";
|
||||
export const BuiltInObjectId = "BuiltInObject";
|
||||
export const BuiltInUseStateId = "BuiltInUseState";
|
||||
export const BuiltInSetStateId = "BuiltInSetState";
|
||||
export const BuiltInUseActionStateId = "BuiltInUseActionState";
|
||||
export const BuiltInSetActionStateId = "BuiltInSetActionState";
|
||||
export const BuiltInUseRefId = "BuiltInUseRefId";
|
||||
export const BuiltInRefValueId = "BuiltInRefValue";
|
||||
export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly";
|
||||
export const BuiltInUseEffectHookId = "BuiltInUseEffectHook";
|
||||
export const BuiltInUseLayoutEffectHookId = "BuiltInUseLayoutEffectHook";
|
||||
export const BuiltInUseInsertionEffectHookId = "BuiltInUseInsertionEffectHook";
|
||||
export const BuiltInUseOperatorId = "BuiltInUseOperator";
|
||||
export const BuiltInUseReducerId = "BuiltInUseReducer";
|
||||
export const BuiltInDispatchId = "BuiltInDispatch";
|
||||
export const BuiltInPropsId = 'BuiltInProps';
|
||||
export const BuiltInArrayId = 'BuiltInArray';
|
||||
export const BuiltInFunctionId = 'BuiltInFunction';
|
||||
export const BuiltInJsxId = 'BuiltInJsx';
|
||||
export const BuiltInObjectId = 'BuiltInObject';
|
||||
export const BuiltInUseStateId = 'BuiltInUseState';
|
||||
export const BuiltInSetStateId = 'BuiltInSetState';
|
||||
export const BuiltInUseActionStateId = 'BuiltInUseActionState';
|
||||
export const BuiltInSetActionStateId = 'BuiltInSetActionState';
|
||||
export const BuiltInUseRefId = 'BuiltInUseRefId';
|
||||
export const BuiltInRefValueId = 'BuiltInRefValue';
|
||||
export const BuiltInMixedReadonlyId = 'BuiltInMixedReadonly';
|
||||
export const BuiltInUseEffectHookId = 'BuiltInUseEffectHook';
|
||||
export const BuiltInUseLayoutEffectHookId = 'BuiltInUseLayoutEffectHook';
|
||||
export const BuiltInUseInsertionEffectHookId = 'BuiltInUseInsertionEffectHook';
|
||||
export const BuiltInUseOperatorId = 'BuiltInUseOperator';
|
||||
export const BuiltInUseReducerId = 'BuiltInUseReducer';
|
||||
export const BuiltInDispatchId = 'BuiltInDispatch';
|
||||
|
||||
// ShapeRegistry with default definitions for built-ins.
|
||||
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
|
||||
|
||||
// If the `ref` prop exists, it has the ref type
|
||||
addObject(BUILTIN_SHAPES, BuiltInPropsId, [
|
||||
["ref", { kind: "Object", shapeId: BuiltInUseRefId }],
|
||||
['ref', {kind: 'Object', shapeId: BuiltInUseRefId}],
|
||||
]);
|
||||
|
||||
/* Built-in array shape */
|
||||
addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
[
|
||||
"indexOf",
|
||||
'indexOf',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"includes",
|
||||
'includes',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"pop",
|
||||
'pop',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
returnType: { kind: "Poly" },
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.Store,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"at",
|
||||
'at',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [Effect.Read],
|
||||
restParam: null,
|
||||
returnType: { kind: "Poly" },
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.Capture,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"concat",
|
||||
'concat',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Capture,
|
||||
returnType: {
|
||||
kind: "Object",
|
||||
kind: 'Object',
|
||||
shapeId: BuiltInArrayId,
|
||||
},
|
||||
calleeEffect: Effect.Capture,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
["length", PRIMITIVE_TYPE],
|
||||
['length', PRIMITIVE_TYPE],
|
||||
[
|
||||
"push",
|
||||
'push',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Capture,
|
||||
@@ -284,12 +284,12 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"slice",
|
||||
'slice',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {
|
||||
kind: "Object",
|
||||
kind: 'Object',
|
||||
shapeId: BuiltInArrayId,
|
||||
},
|
||||
calleeEffect: Effect.Capture,
|
||||
@@ -297,11 +297,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"map",
|
||||
'map',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Object", shapeId: BuiltInArrayId },
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
/*
|
||||
* callee is ConditionallyMutate because items of the array
|
||||
* flow into the lambda and may be mutated there, even though
|
||||
@@ -314,11 +314,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"filter",
|
||||
'filter',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Object", shapeId: BuiltInArrayId },
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
/*
|
||||
* callee is ConditionallyMutate because items of the array
|
||||
* flow into the lambda and may be mutated there, even though
|
||||
@@ -331,11 +331,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"every",
|
||||
'every',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
/*
|
||||
* callee is ConditionallyMutate because items of the array
|
||||
* flow into the lambda and may be mutated there, even though
|
||||
@@ -348,11 +348,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"some",
|
||||
'some',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
/*
|
||||
* callee is ConditionallyMutate because items of the array
|
||||
* flow into the lambda and may be mutated there, even though
|
||||
@@ -365,11 +365,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"find",
|
||||
'find',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Poly" },
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
@@ -377,11 +377,11 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"findIndex",
|
||||
'findIndex',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
/*
|
||||
* callee is ConditionallyMutate because items of the array
|
||||
* flow into the lambda and may be mutated there, even though
|
||||
@@ -394,7 +394,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"join",
|
||||
'join',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
@@ -409,7 +409,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
/* Built-in Object shape */
|
||||
addObject(BUILTIN_SHAPES, BuiltInObjectId, [
|
||||
[
|
||||
"toString",
|
||||
'toString',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: null,
|
||||
@@ -425,9 +425,9 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
|
||||
["0", { kind: "Poly" }],
|
||||
['0', {kind: 'Poly'}],
|
||||
[
|
||||
"1",
|
||||
'1',
|
||||
addFunction(
|
||||
BUILTIN_SHAPES,
|
||||
[],
|
||||
@@ -438,15 +438,15 @@ addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
},
|
||||
BuiltInSetStateId
|
||||
BuiltInSetStateId,
|
||||
),
|
||||
],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
|
||||
["0", { kind: "Poly" }],
|
||||
['0', {kind: 'Poly'}],
|
||||
[
|
||||
"1",
|
||||
'1',
|
||||
addFunction(
|
||||
BUILTIN_SHAPES,
|
||||
[],
|
||||
@@ -457,15 +457,15 @@ addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
},
|
||||
BuiltInSetActionStateId
|
||||
BuiltInSetActionStateId,
|
||||
),
|
||||
],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInUseReducerId, [
|
||||
["0", { kind: "Poly" }],
|
||||
['0', {kind: 'Poly'}],
|
||||
[
|
||||
"1",
|
||||
'1',
|
||||
addFunction(
|
||||
BUILTIN_SHAPES,
|
||||
[],
|
||||
@@ -476,22 +476,22 @@ addObject(BUILTIN_SHAPES, BuiltInUseReducerId, [
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
},
|
||||
BuiltInDispatchId
|
||||
BuiltInDispatchId,
|
||||
),
|
||||
],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInUseRefId, [
|
||||
["current", { kind: "Object", shapeId: BuiltInRefValueId }],
|
||||
['current', {kind: 'Object', shapeId: BuiltInRefValueId}],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInRefValueId, [
|
||||
["*", { kind: "Object", shapeId: BuiltInRefValueId }],
|
||||
['*', {kind: 'Object', shapeId: BuiltInRefValueId}],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
[
|
||||
"toString",
|
||||
'toString',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
@@ -501,34 +501,34 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"map",
|
||||
'map',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: { kind: "Object", shapeId: BuiltInArrayId },
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"filter",
|
||||
'filter',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: { kind: "Object", shapeId: BuiltInArrayId },
|
||||
returnType: {kind: 'Object', shapeId: BuiltInArrayId},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"concat",
|
||||
'concat',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Capture,
|
||||
returnType: {
|
||||
kind: "Object",
|
||||
kind: 'Object',
|
||||
shapeId: BuiltInArrayId,
|
||||
},
|
||||
calleeEffect: Effect.Capture,
|
||||
@@ -536,12 +536,12 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"slice",
|
||||
'slice',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {
|
||||
kind: "Object",
|
||||
kind: 'Object',
|
||||
shapeId: BuiltInArrayId,
|
||||
},
|
||||
calleeEffect: Effect.Capture,
|
||||
@@ -549,11 +549,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"every",
|
||||
'every',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
noAlias: true,
|
||||
@@ -561,11 +561,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"some",
|
||||
'some',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
noAlias: true,
|
||||
@@ -573,11 +573,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"find",
|
||||
'find',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Poly" },
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
@@ -585,11 +585,11 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"findIndex",
|
||||
'findIndex',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Primitive" },
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.ConditionallyMutate,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
noAlias: true,
|
||||
@@ -597,7 +597,7 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
}),
|
||||
],
|
||||
[
|
||||
"join",
|
||||
'join',
|
||||
addFunction(BUILTIN_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
@@ -606,7 +606,7 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
["*", { kind: "Object", shapeId: BuiltInMixedReadonlyId }],
|
||||
['*', {kind: 'Object', shapeId: BuiltInMixedReadonlyId}],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInJsxId, []);
|
||||
@@ -617,12 +617,12 @@ export const DefaultMutatingHook = addHook(
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.ConditionallyMutate,
|
||||
returnType: { kind: "Poly" },
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: "Custom",
|
||||
hookKind: 'Custom',
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
},
|
||||
"DefaultMutatingHook"
|
||||
'DefaultMutatingHook',
|
||||
);
|
||||
|
||||
export const DefaultNonmutatingHook = addHook(
|
||||
@@ -630,10 +630,10 @@ export const DefaultNonmutatingHook = addHook(
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: { kind: "Poly" },
|
||||
returnType: {kind: 'Poly'},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: "Custom",
|
||||
hookKind: 'Custom',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
},
|
||||
"DefaultNonmutatingHook"
|
||||
'DefaultNonmutatingHook',
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { CompilerError } from "..";
|
||||
import { BlockId, GotoVariant, HIRFunction } from "./HIR";
|
||||
import {CompilerError} from '..';
|
||||
import {BlockId, GotoVariant, HIRFunction} from './HIR';
|
||||
|
||||
export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
|
||||
const merged: Array<{
|
||||
@@ -10,16 +10,16 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
|
||||
const rewrites: Map<BlockId, BlockId> = new Map();
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
const terminal = block.terminal;
|
||||
if (terminal.kind === "label") {
|
||||
const { block: nextId, fallthrough: fallthroughId } = terminal;
|
||||
if (terminal.kind === 'label') {
|
||||
const {block: nextId, fallthrough: fallthroughId} = terminal;
|
||||
const next = fn.body.blocks.get(nextId)!;
|
||||
const fallthrough = fn.body.blocks.get(fallthroughId)!;
|
||||
if (
|
||||
next.terminal.kind === "goto" &&
|
||||
next.terminal.kind === 'goto' &&
|
||||
next.terminal.variant === GotoVariant.Break &&
|
||||
next.terminal.block === fallthroughId
|
||||
) {
|
||||
if (next.kind === "block" && fallthrough.kind === "block") {
|
||||
if (next.kind === 'block' && fallthrough.kind === 'block') {
|
||||
// Only merge normal block types
|
||||
merged.push({
|
||||
label: blockId,
|
||||
@@ -45,9 +45,9 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
|
||||
CompilerError.invariant(
|
||||
next.phis.size === 0 && fallthrough.phis.size === 0,
|
||||
{
|
||||
reason: "Unexpected phis when merging label blocks",
|
||||
reason: 'Unexpected phis when merging label blocks',
|
||||
loc: label.terminal.loc,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
CompilerError.invariant(
|
||||
@@ -56,9 +56,9 @@ export function pruneUnusedLabelsHIR(fn: HIRFunction): void {
|
||||
next.preds.has(originalLabelId) &&
|
||||
fallthrough.preds.has(nextId),
|
||||
{
|
||||
reason: "Unexpected block predecessors when merging label blocks",
|
||||
reason: 'Unexpected block predecessors when merging label blocks',
|
||||
loc: label.terminal.loc,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
label.instructions.push(...next.instructions, ...fallthrough.instructions);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
|
||||
export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
|
||||
|
||||
@@ -16,7 +16,7 @@ export type Type =
|
||||
| PolyType
|
||||
| PropType
|
||||
| ObjectMethod;
|
||||
export type PrimitiveType = { kind: "Primitive" };
|
||||
export type PrimitiveType = {kind: 'Primitive'};
|
||||
|
||||
/*
|
||||
* An {@link FunctionType} or {@link ObjectType} (also a JS object) may be associated with an
|
||||
@@ -34,36 +34,36 @@ export type PrimitiveType = { kind: "Primitive" };
|
||||
*/
|
||||
|
||||
export type FunctionType = {
|
||||
kind: "Function";
|
||||
kind: 'Function';
|
||||
shapeId: string | null;
|
||||
return: Type;
|
||||
};
|
||||
|
||||
export type ObjectType = {
|
||||
kind: "Object";
|
||||
kind: 'Object';
|
||||
shapeId: string | null;
|
||||
};
|
||||
|
||||
export type TypeVar = {
|
||||
kind: "Type";
|
||||
kind: 'Type';
|
||||
id: TypeId;
|
||||
};
|
||||
export type PolyType = {
|
||||
kind: "Poly";
|
||||
kind: 'Poly';
|
||||
};
|
||||
export type PhiType = {
|
||||
kind: "Phi";
|
||||
kind: 'Phi';
|
||||
operands: Array<Type>;
|
||||
};
|
||||
export type PropType = {
|
||||
kind: "Property";
|
||||
kind: 'Property';
|
||||
objectType: Type;
|
||||
objectName: string;
|
||||
propertyName: string;
|
||||
};
|
||||
|
||||
export type ObjectMethod = {
|
||||
kind: "ObjectMethod";
|
||||
kind: 'ObjectMethod';
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -71,11 +71,11 @@ export type ObjectMethod = {
|
||||
* accidentally.
|
||||
*/
|
||||
const opaqueTypeId = Symbol();
|
||||
export type TypeId = number & { [opaqueTypeId]: "IdentifierId" };
|
||||
export type TypeId = number & {[opaqueTypeId]: 'IdentifierId'};
|
||||
|
||||
export function makeTypeId(id: number): TypeId {
|
||||
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
|
||||
reason: "Expected instruction id to be a non-negative integer",
|
||||
reason: 'Expected instruction id to be a non-negative integer',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
@@ -86,7 +86,7 @@ export function makeTypeId(id: number): TypeId {
|
||||
let typeCounter = 0;
|
||||
export function makeType(): TypeVar {
|
||||
return {
|
||||
kind: "Type",
|
||||
kind: 'Type',
|
||||
id: makeTypeId(typeCounter++),
|
||||
};
|
||||
}
|
||||
@@ -97,40 +97,40 @@ export function makeType(): TypeVar {
|
||||
*/
|
||||
export function duplicateType(type: Type): Type {
|
||||
switch (type.kind) {
|
||||
case "Function": {
|
||||
case 'Function': {
|
||||
return {
|
||||
kind: "Function",
|
||||
kind: 'Function',
|
||||
return: duplicateType(type.return),
|
||||
shapeId: type.shapeId,
|
||||
};
|
||||
}
|
||||
case "Object": {
|
||||
return { kind: "Object", shapeId: type.shapeId };
|
||||
case 'Object': {
|
||||
return {kind: 'Object', shapeId: type.shapeId};
|
||||
}
|
||||
case "ObjectMethod": {
|
||||
return { kind: "ObjectMethod" };
|
||||
case 'ObjectMethod': {
|
||||
return {kind: 'ObjectMethod'};
|
||||
}
|
||||
case "Phi": {
|
||||
case 'Phi': {
|
||||
return {
|
||||
kind: "Phi",
|
||||
operands: type.operands.map((operand) => duplicateType(operand)),
|
||||
kind: 'Phi',
|
||||
operands: type.operands.map(operand => duplicateType(operand)),
|
||||
};
|
||||
}
|
||||
case "Poly": {
|
||||
return { kind: "Poly" };
|
||||
case 'Poly': {
|
||||
return {kind: 'Poly'};
|
||||
}
|
||||
case "Primitive": {
|
||||
return { kind: "Primitive" };
|
||||
case 'Primitive': {
|
||||
return {kind: 'Primitive'};
|
||||
}
|
||||
case "Property": {
|
||||
case 'Property': {
|
||||
return {
|
||||
kind: "Property",
|
||||
kind: 'Property',
|
||||
objectType: duplicateType(type.objectType),
|
||||
objectName: type.objectName,
|
||||
propertyName: type.propertyName,
|
||||
};
|
||||
}
|
||||
case "Type": {
|
||||
case 'Type': {
|
||||
return makeType();
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ export function typeEquals(tA: Type, tB: Type): boolean {
|
||||
}
|
||||
|
||||
function typeVarEquals(tA: Type, tB: Type): boolean {
|
||||
if (tA.kind === "Type" && tB.kind === "Type") {
|
||||
if (tA.kind === 'Type' && tB.kind === 'Type') {
|
||||
return tA.id === tB.id;
|
||||
}
|
||||
return false;
|
||||
@@ -162,11 +162,11 @@ function typeKindCheck(tA: Type, tb: Type, type: string): boolean {
|
||||
}
|
||||
|
||||
function objectMethodTypeEquals(tA: Type, tB: Type): boolean {
|
||||
return typeKindCheck(tA, tB, "ObjectMethod");
|
||||
return typeKindCheck(tA, tB, 'ObjectMethod');
|
||||
}
|
||||
|
||||
function propTypeEquals(tA: Type, tB: Type): boolean {
|
||||
if (tA.kind === "Property" && tB.kind === "Property") {
|
||||
if (tA.kind === 'Property' && tB.kind === 'Property') {
|
||||
if (!typeEquals(tA.objectType, tB.objectType)) {
|
||||
return false;
|
||||
}
|
||||
@@ -180,15 +180,15 @@ function propTypeEquals(tA: Type, tB: Type): boolean {
|
||||
}
|
||||
|
||||
function primitiveTypeEquals(tA: Type, tB: Type): boolean {
|
||||
return typeKindCheck(tA, tB, "Primitive");
|
||||
return typeKindCheck(tA, tB, 'Primitive');
|
||||
}
|
||||
|
||||
function polyTypeEquals(tA: Type, tB: Type): boolean {
|
||||
return typeKindCheck(tA, tB, "Poly");
|
||||
return typeKindCheck(tA, tB, 'Poly');
|
||||
}
|
||||
|
||||
function objectTypeEquals(tA: Type, tB: Type): boolean {
|
||||
if (tA.kind === "Object" && tB.kind == "Object") {
|
||||
if (tA.kind === 'Object' && tB.kind == 'Object') {
|
||||
return tA.shapeId === tB.shapeId;
|
||||
}
|
||||
|
||||
@@ -196,14 +196,14 @@ function objectTypeEquals(tA: Type, tB: Type): boolean {
|
||||
}
|
||||
|
||||
function funcTypeEquals(tA: Type, tB: Type): boolean {
|
||||
if (tA.kind !== "Function" || tB.kind !== "Function") {
|
||||
if (tA.kind !== 'Function' || tB.kind !== 'Function') {
|
||||
return false;
|
||||
}
|
||||
return typeEquals(tA.return, tB.return);
|
||||
}
|
||||
|
||||
function phiTypeEquals(tA: Type, tB: Type): boolean {
|
||||
if (tA.kind === "Phi" && tB.kind === "Phi") {
|
||||
if (tA.kind === 'Phi' && tB.kind === 'Phi') {
|
||||
if (tA.operands.length !== tB.operands.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export { assertConsistentIdentifiers } from "./AssertConsistentIdentifiers";
|
||||
export {assertConsistentIdentifiers} from './AssertConsistentIdentifiers';
|
||||
export {
|
||||
assertTerminalSuccessorsExist,
|
||||
assertTerminalPredsExist,
|
||||
} from "./AssertTerminalBlocksExist";
|
||||
export { assertValidBlockNesting } from "./AssertValidBlockNesting";
|
||||
export { assertValidMutableRanges } from "./AssertValidMutableRanges";
|
||||
export { lower } from "./BuildHIR";
|
||||
export { buildReactiveScopeTerminalsHIR } from "./BuildReactiveScopeTerminalsHIR";
|
||||
export { computeDominatorTree, computePostDominatorTree } from "./Dominator";
|
||||
} from './AssertTerminalBlocksExist';
|
||||
export {assertValidBlockNesting} from './AssertValidBlockNesting';
|
||||
export {assertValidMutableRanges} from './AssertValidMutableRanges';
|
||||
export {lower} from './BuildHIR';
|
||||
export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR';
|
||||
export {computeDominatorTree, computePostDominatorTree} from './Dominator';
|
||||
export {
|
||||
Environment,
|
||||
parseConfigPragma,
|
||||
@@ -22,15 +22,15 @@ export {
|
||||
type EnvironmentConfig,
|
||||
type ExternalFunction,
|
||||
type Hook,
|
||||
} from "./Environment";
|
||||
export * from "./HIR";
|
||||
} from './Environment';
|
||||
export * from './HIR';
|
||||
export {
|
||||
markInstructionIds,
|
||||
markPredecessors,
|
||||
removeUnnecessaryTryCatch,
|
||||
reversePostorderBlocks,
|
||||
} from "./HIRBuilder";
|
||||
export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks";
|
||||
export { mergeOverlappingReactiveScopesHIR } from "./MergeOverlappingReactiveScopesHIR";
|
||||
export { printFunction, printHIR } from "./PrintHIR";
|
||||
export { pruneUnusedLabelsHIR } from "./PruneUnusedLabelsHIR";
|
||||
} from './HIRBuilder';
|
||||
export {mergeConsecutiveBlocks} from './MergeConsecutiveBlocks';
|
||||
export {mergeOverlappingReactiveScopesHIR} from './MergeOverlappingReactiveScopesHIR';
|
||||
export {printFunction, printHIR} from './PrintHIR';
|
||||
export {pruneUnusedLabelsHIR} from './PruneUnusedLabelsHIR';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "../CompilerError";
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
Effect,
|
||||
HIRFunction,
|
||||
@@ -17,14 +17,14 @@ import {
|
||||
isRefValueType,
|
||||
isUseRefType,
|
||||
makeInstructionId,
|
||||
} from "../HIR";
|
||||
import { deadCodeElimination } from "../Optimization";
|
||||
import { inferReactiveScopeVariables } from "../ReactiveScopes";
|
||||
import { leaveSSA } from "../SSA";
|
||||
import { logHIRFunction } from "../Utils/logger";
|
||||
import { inferMutableContextVariables } from "./InferMutableContextVariables";
|
||||
import { inferMutableRanges } from "./InferMutableRanges";
|
||||
import inferReferenceEffects from "./InferReferenceEffects";
|
||||
} from '../HIR';
|
||||
import {deadCodeElimination} from '../Optimization';
|
||||
import {inferReactiveScopeVariables} from '../ReactiveScopes';
|
||||
import {leaveSSA} from '../SSA';
|
||||
import {logHIRFunction} from '../Utils/logger';
|
||||
import {inferMutableContextVariables} from './InferMutableContextVariables';
|
||||
import {inferMutableRanges} from './InferMutableRanges';
|
||||
import inferReferenceEffects from './InferReferenceEffects';
|
||||
|
||||
// Helper class to track indirections such as LoadLocal and PropertyLoad.
|
||||
export class IdentifierState {
|
||||
@@ -42,7 +42,7 @@ export class IdentifierState {
|
||||
const objectDependency = this.properties.get(object.identifier);
|
||||
let nextDependency: ReactiveScopeDependency;
|
||||
if (objectDependency === undefined) {
|
||||
nextDependency = { identifier: object.identifier, path: [property] };
|
||||
nextDependency = {identifier: object.identifier, path: [property]};
|
||||
} else {
|
||||
nextDependency = {
|
||||
identifier: objectDependency.identifier,
|
||||
@@ -54,7 +54,7 @@ export class IdentifierState {
|
||||
|
||||
declareTemporary(lvalue: Place, value: Place): void {
|
||||
const resolved: ReactiveScopeDependency = this.properties.get(
|
||||
value.identifier
|
||||
value.identifier,
|
||||
) ?? {
|
||||
identifier: value.identifier,
|
||||
path: [],
|
||||
@@ -69,30 +69,30 @@ export default function analyseFunctions(func: HIRFunction): void {
|
||||
for (const [_, block] of func.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
switch (instr.value.kind) {
|
||||
case "ObjectMethod":
|
||||
case "FunctionExpression": {
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression': {
|
||||
lower(instr.value.loweredFunc.func);
|
||||
infer(instr.value.loweredFunc, state, func.context);
|
||||
break;
|
||||
}
|
||||
case "PropertyLoad": {
|
||||
case 'PropertyLoad': {
|
||||
state.declareProperty(
|
||||
instr.lvalue,
|
||||
instr.value.object,
|
||||
instr.value.property
|
||||
instr.value.property,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "ComputedLoad": {
|
||||
case 'ComputedLoad': {
|
||||
/*
|
||||
* The path is set to an empty string as the path doesn't really
|
||||
* matter for a computed load.
|
||||
*/
|
||||
state.declareProperty(instr.lvalue, instr.value.object, "");
|
||||
state.declareProperty(instr.lvalue, instr.value.object, '');
|
||||
break;
|
||||
}
|
||||
case "LoadLocal":
|
||||
case "LoadContext": {
|
||||
case 'LoadLocal':
|
||||
case 'LoadContext': {
|
||||
if (instr.lvalue.identifier.name === null) {
|
||||
state.declareTemporary(instr.lvalue, instr.value.place);
|
||||
}
|
||||
@@ -105,19 +105,19 @@ export default function analyseFunctions(func: HIRFunction): void {
|
||||
|
||||
function lower(func: HIRFunction): void {
|
||||
analyseFunctions(func);
|
||||
inferReferenceEffects(func, { isFunctionExpression: true });
|
||||
inferReferenceEffects(func, {isFunctionExpression: true});
|
||||
deadCodeElimination(func);
|
||||
inferMutableRanges(func);
|
||||
leaveSSA(func);
|
||||
inferReactiveScopeVariables(func);
|
||||
inferMutableContextVariables(func);
|
||||
logHIRFunction("AnalyseFunction (inner)", func);
|
||||
logHIRFunction('AnalyseFunction (inner)', func);
|
||||
}
|
||||
|
||||
function infer(
|
||||
loweredFunc: LoweredFunction,
|
||||
state: IdentifierState,
|
||||
context: Array<Place>
|
||||
context: Array<Place>,
|
||||
): void {
|
||||
const mutations = new Map<string, Effect>();
|
||||
for (const operand of loweredFunc.func.context) {
|
||||
@@ -166,7 +166,7 @@ function infer(
|
||||
*/
|
||||
for (const place of context) {
|
||||
CompilerError.invariant(place.identifier.name !== null, {
|
||||
reason: "context refs should always have a name",
|
||||
reason: 'context refs should always have a name',
|
||||
description: null,
|
||||
loc: place.loc,
|
||||
suggestions: null,
|
||||
|
||||
+50
-50
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError, SourceLocation } from "..";
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {
|
||||
CallExpression,
|
||||
Effect,
|
||||
@@ -28,11 +28,11 @@ import {
|
||||
TInstruction,
|
||||
getHookKindForType,
|
||||
makeInstructionId,
|
||||
} from "../HIR";
|
||||
import { createTemporaryPlace, markInstructionIds } from "../HIR/HIRBuilder";
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace, markInstructionIds} from '../HIR/HIRBuilder';
|
||||
|
||||
type ManualMemoCallee = {
|
||||
kind: "useMemo" | "useCallback";
|
||||
kind: 'useMemo' | 'useCallback';
|
||||
loadInstr: TInstruction<LoadGlobal> | TInstruction<PropertyLoad>;
|
||||
};
|
||||
|
||||
@@ -51,19 +51,19 @@ type IdentifierSidemap = {
|
||||
*/
|
||||
export function collectMaybeMemoDependencies(
|
||||
value: InstructionValue,
|
||||
maybeDeps: Map<IdentifierId, ManualMemoDependency>
|
||||
maybeDeps: Map<IdentifierId, ManualMemoDependency>,
|
||||
): ManualMemoDependency | null {
|
||||
switch (value.kind) {
|
||||
case "LoadGlobal": {
|
||||
case 'LoadGlobal': {
|
||||
return {
|
||||
root: {
|
||||
kind: "Global",
|
||||
kind: 'Global',
|
||||
identifierName: value.binding.name,
|
||||
},
|
||||
path: [],
|
||||
};
|
||||
}
|
||||
case "PropertyLoad": {
|
||||
case 'PropertyLoad': {
|
||||
const object = maybeDeps.get(value.object.identifier.id);
|
||||
if (object != null) {
|
||||
return {
|
||||
@@ -74,26 +74,26 @@ export function collectMaybeMemoDependencies(
|
||||
break;
|
||||
}
|
||||
|
||||
case "LoadLocal":
|
||||
case "LoadContext": {
|
||||
case 'LoadLocal':
|
||||
case 'LoadContext': {
|
||||
const source = maybeDeps.get(value.place.identifier.id);
|
||||
if (source != null) {
|
||||
return source;
|
||||
} else if (
|
||||
value.place.identifier.name != null &&
|
||||
value.place.identifier.name.kind === "named"
|
||||
value.place.identifier.name.kind === 'named'
|
||||
) {
|
||||
return {
|
||||
root: {
|
||||
kind: "NamedLocal",
|
||||
value: { ...value.place },
|
||||
kind: 'NamedLocal',
|
||||
value: {...value.place},
|
||||
},
|
||||
path: [],
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "StoreLocal": {
|
||||
case 'StoreLocal': {
|
||||
/*
|
||||
* Value blocks rely on StoreLocal to populate their return value.
|
||||
* We need to track these as optional property chains are valid in
|
||||
@@ -102,7 +102,7 @@ export function collectMaybeMemoDependencies(
|
||||
const lvalue = value.lvalue.place.identifier;
|
||||
const rvalue = value.value.identifier.id;
|
||||
const aliased = maybeDeps.get(rvalue);
|
||||
if (aliased != null && lvalue.name?.kind !== "named") {
|
||||
if (aliased != null && lvalue.name?.kind !== 'named') {
|
||||
maybeDeps.set(lvalue.id, aliased);
|
||||
return aliased;
|
||||
}
|
||||
@@ -115,34 +115,34 @@ export function collectMaybeMemoDependencies(
|
||||
function collectTemporaries(
|
||||
instr: Instruction,
|
||||
env: Environment,
|
||||
sidemap: IdentifierSidemap
|
||||
sidemap: IdentifierSidemap,
|
||||
): void {
|
||||
const { value, lvalue } = instr;
|
||||
const {value, lvalue} = instr;
|
||||
switch (value.kind) {
|
||||
case "FunctionExpression": {
|
||||
case 'FunctionExpression': {
|
||||
sidemap.functions.set(
|
||||
instr.lvalue.identifier.id,
|
||||
instr as TInstruction<FunctionExpression>
|
||||
instr as TInstruction<FunctionExpression>,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "LoadGlobal": {
|
||||
case 'LoadGlobal': {
|
||||
const global = env.getGlobalDeclaration(value.binding);
|
||||
const hookKind = global !== null ? getHookKindForType(env, global) : null;
|
||||
const lvalId = instr.lvalue.identifier.id;
|
||||
if (hookKind === "useMemo" || hookKind === "useCallback") {
|
||||
if (hookKind === 'useMemo' || hookKind === 'useCallback') {
|
||||
sidemap.manualMemos.set(lvalId, {
|
||||
kind: hookKind,
|
||||
loadInstr: instr as TInstruction<LoadGlobal>,
|
||||
});
|
||||
} else if (value.binding.name === "React") {
|
||||
} else if (value.binding.name === 'React') {
|
||||
sidemap.react.add(lvalId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "PropertyLoad": {
|
||||
case 'PropertyLoad': {
|
||||
if (sidemap.react.has(value.object.identifier.id)) {
|
||||
if (value.property === "useMemo" || value.property === "useCallback") {
|
||||
if (value.property === 'useMemo' || value.property === 'useCallback') {
|
||||
sidemap.manualMemos.set(instr.lvalue.identifier.id, {
|
||||
kind: value.property,
|
||||
loadInstr: instr as TInstruction<PropertyLoad>,
|
||||
@@ -151,11 +151,11 @@ function collectTemporaries(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ArrayExpression": {
|
||||
if (value.elements.every((e) => e.kind === "Identifier")) {
|
||||
case 'ArrayExpression': {
|
||||
if (value.elements.every(e => e.kind === 'Identifier')) {
|
||||
sidemap.maybeDepsLists.set(
|
||||
instr.lvalue.identifier.id,
|
||||
value.elements as Array<Place>
|
||||
value.elements as Array<Place>,
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -173,14 +173,14 @@ function makeManualMemoizationMarkers(
|
||||
env: Environment,
|
||||
depsList: Array<ManualMemoDependency> | null,
|
||||
memoDecl: Place,
|
||||
manualMemoId: number
|
||||
manualMemoId: number,
|
||||
): [TInstruction<StartMemoize>, TInstruction<FinishMemoize>] {
|
||||
return [
|
||||
{
|
||||
id: makeInstructionId(0),
|
||||
lvalue: createTemporaryPlace(env, fnExpr.loc),
|
||||
value: {
|
||||
kind: "StartMemoize",
|
||||
kind: 'StartMemoize',
|
||||
manualMemoId,
|
||||
/*
|
||||
* Use deps list from source instead of inferred deps
|
||||
@@ -195,9 +195,9 @@ function makeManualMemoizationMarkers(
|
||||
id: makeInstructionId(0),
|
||||
lvalue: createTemporaryPlace(env, fnExpr.loc),
|
||||
value: {
|
||||
kind: "FinishMemoize",
|
||||
kind: 'FinishMemoize',
|
||||
manualMemoId,
|
||||
decl: { ...memoDecl },
|
||||
decl: {...memoDecl},
|
||||
loc: fnExpr.loc,
|
||||
},
|
||||
loc: fnExpr.loc,
|
||||
@@ -208,9 +208,9 @@ function makeManualMemoizationMarkers(
|
||||
function getManualMemoizationReplacement(
|
||||
fn: Place,
|
||||
loc: SourceLocation,
|
||||
kind: "useMemo" | "useCallback"
|
||||
kind: 'useMemo' | 'useCallback',
|
||||
): LoadLocal | CallExpression {
|
||||
if (kind === "useMemo") {
|
||||
if (kind === 'useMemo') {
|
||||
/*
|
||||
* Replace the hook callee with the fn arg.
|
||||
*
|
||||
@@ -230,7 +230,7 @@ function getManualMemoizationReplacement(
|
||||
* inline the useMemo callback along with any other immediately invoked IIFEs.
|
||||
*/
|
||||
return {
|
||||
kind: "CallExpression",
|
||||
kind: 'CallExpression',
|
||||
callee: fn,
|
||||
/*
|
||||
* Drop the args, including the deps array which DCE will remove
|
||||
@@ -256,9 +256,9 @@ function getManualMemoizationReplacement(
|
||||
* $4 = LoadLocal $2 // reference the function
|
||||
*/
|
||||
return {
|
||||
kind: "LoadLocal",
|
||||
kind: 'LoadLocal',
|
||||
place: {
|
||||
kind: "Identifier",
|
||||
kind: 'Identifier',
|
||||
identifier: fn.identifier,
|
||||
effect: Effect.Unknown,
|
||||
reactive: false,
|
||||
@@ -271,8 +271,8 @@ function getManualMemoizationReplacement(
|
||||
|
||||
function extractManualMemoizationArgs(
|
||||
instr: TInstruction<CallExpression> | TInstruction<MethodCall>,
|
||||
kind: "useCallback" | "useMemo",
|
||||
sidemap: IdentifierSidemap
|
||||
kind: 'useCallback' | 'useMemo',
|
||||
sidemap: IdentifierSidemap,
|
||||
): {
|
||||
fnPlace: Place;
|
||||
depsList: Array<ManualMemoDependency> | null;
|
||||
@@ -287,7 +287,7 @@ function extractManualMemoizationArgs(
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
if (fnPlace.kind === "Spread" || depsListPlace?.kind === "Spread") {
|
||||
if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') {
|
||||
CompilerError.throwInvalidReact({
|
||||
reason: `Unexpected spread argument to ${kind}`,
|
||||
loc: instr.value.loc,
|
||||
@@ -297,7 +297,7 @@ function extractManualMemoizationArgs(
|
||||
let depsList: Array<ManualMemoDependency> | null = null;
|
||||
if (depsListPlace != null) {
|
||||
const maybeDepsList = sidemap.maybeDepsLists.get(
|
||||
depsListPlace.identifier.id
|
||||
depsListPlace.identifier.id,
|
||||
);
|
||||
if (maybeDepsList == null) {
|
||||
CompilerError.throwInvalidReact({
|
||||
@@ -306,7 +306,7 @@ function extractManualMemoizationArgs(
|
||||
loc: depsListPlace.loc,
|
||||
});
|
||||
}
|
||||
depsList = maybeDepsList.map((dep) => {
|
||||
depsList = maybeDepsList.map(dep => {
|
||||
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
|
||||
if (maybeDep == null) {
|
||||
CompilerError.throwInvalidReact({
|
||||
@@ -362,25 +362,25 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
for (let i = 0; i < block.instructions.length; i++) {
|
||||
const instr = block.instructions[i]!;
|
||||
if (
|
||||
instr.value.kind === "CallExpression" ||
|
||||
instr.value.kind === "MethodCall"
|
||||
instr.value.kind === 'CallExpression' ||
|
||||
instr.value.kind === 'MethodCall'
|
||||
) {
|
||||
const id =
|
||||
instr.value.kind === "CallExpression"
|
||||
instr.value.kind === 'CallExpression'
|
||||
? instr.value.callee.identifier.id
|
||||
: instr.value.property.identifier.id;
|
||||
|
||||
const manualMemo = sidemap.manualMemos.get(id);
|
||||
if (manualMemo != null) {
|
||||
const { fnPlace, depsList } = extractManualMemoizationArgs(
|
||||
const {fnPlace, depsList} = extractManualMemoizationArgs(
|
||||
instr as TInstruction<CallExpression> | TInstruction<MethodCall>,
|
||||
manualMemo.kind,
|
||||
sidemap
|
||||
sidemap,
|
||||
);
|
||||
instr.value = getManualMemoizationReplacement(
|
||||
fnPlace,
|
||||
instr.value.loc,
|
||||
manualMemo.kind
|
||||
manualMemo.kind,
|
||||
);
|
||||
if (isValidationEnabled) {
|
||||
/**
|
||||
@@ -404,10 +404,10 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
});
|
||||
}
|
||||
const memoDecl: Place =
|
||||
manualMemo.kind === "useMemo"
|
||||
manualMemo.kind === 'useMemo'
|
||||
? instr.lvalue
|
||||
: {
|
||||
kind: "Identifier",
|
||||
kind: 'Identifier',
|
||||
identifier: fnPlace.identifier,
|
||||
effect: Effect.Unknown,
|
||||
reactive: false,
|
||||
@@ -419,7 +419,7 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
func.env,
|
||||
depsList,
|
||||
memoDecl,
|
||||
nextManualMemoId++
|
||||
nextManualMemoId++,
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
Instruction,
|
||||
isPrimitiveType,
|
||||
Place,
|
||||
} from "../HIR/HIR";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
} from '../HIR/HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
export type AliasSet = Set<Identifier>;
|
||||
|
||||
@@ -29,34 +29,34 @@ export function inferAliases(func: HIRFunction): DisjointSet<Identifier> {
|
||||
|
||||
function inferInstr(
|
||||
instr: Instruction,
|
||||
aliases: DisjointSet<Identifier>
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
const { lvalue, value: instrValue } = instr;
|
||||
const {lvalue, value: instrValue} = instr;
|
||||
let alias: Place | null = null;
|
||||
switch (instrValue.kind) {
|
||||
case "LoadLocal":
|
||||
case "LoadContext": {
|
||||
case 'LoadLocal':
|
||||
case 'LoadContext': {
|
||||
if (isPrimitiveType(instrValue.place.identifier)) {
|
||||
return;
|
||||
}
|
||||
alias = instrValue.place;
|
||||
break;
|
||||
}
|
||||
case "StoreLocal":
|
||||
case "StoreContext": {
|
||||
case 'StoreLocal':
|
||||
case 'StoreContext': {
|
||||
alias = instrValue.value;
|
||||
break;
|
||||
}
|
||||
case "Destructure": {
|
||||
case 'Destructure': {
|
||||
alias = instrValue.value;
|
||||
break;
|
||||
}
|
||||
case "ComputedLoad":
|
||||
case "PropertyLoad": {
|
||||
case 'ComputedLoad':
|
||||
case 'PropertyLoad': {
|
||||
alias = instrValue.object;
|
||||
break;
|
||||
}
|
||||
case "TypeCastExpression": {
|
||||
case 'TypeCastExpression': {
|
||||
alias = instrValue.value;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { HIRFunction, Identifier } from "../HIR/HIR";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import {HIRFunction, Identifier} from '../HIR/HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
export function inferAliasForPhis(
|
||||
func: HIRFunction,
|
||||
aliases: DisjointSet<Identifier>
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
for (const [_, block] of func.body.blocks) {
|
||||
for (const phi of block.phis) {
|
||||
|
||||
@@ -11,20 +11,20 @@ import {
|
||||
Identifier,
|
||||
InstructionId,
|
||||
Place,
|
||||
} from "../HIR/HIR";
|
||||
} from '../HIR/HIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
} from "../HIR/visitors";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
export function inferAliasForStores(
|
||||
func: HIRFunction,
|
||||
aliases: DisjointSet<Identifier>
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
for (const [_, block] of func.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
const { value, lvalue } = instr;
|
||||
const {value, lvalue} = instr;
|
||||
const isStore =
|
||||
lvalue.effect === Effect.Store ||
|
||||
/*
|
||||
@@ -32,7 +32,7 @@ export function inferAliasForStores(
|
||||
* as Effect.Store.
|
||||
*/
|
||||
![...eachInstructionValueOperand(value)].every(
|
||||
(operand) => operand.effect !== Effect.Store
|
||||
operand => operand.effect !== Effect.Store,
|
||||
);
|
||||
|
||||
if (!isStore) {
|
||||
@@ -57,7 +57,7 @@ function maybeAlias(
|
||||
aliases: DisjointSet<Identifier>,
|
||||
lvalue: Place,
|
||||
rvalue: Place,
|
||||
id: InstructionId
|
||||
id: InstructionId,
|
||||
): void {
|
||||
if (
|
||||
lvalue.identifier.mutableRange.end > id + 1 ||
|
||||
|
||||
+10
-10
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Effect, HIRFunction, Identifier, Place } from "../HIR";
|
||||
import {Effect, HIRFunction, Identifier, Place} from '../HIR';
|
||||
import {
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from "../HIR/visitors";
|
||||
import { IdentifierState } from "./AnalyseFunctions";
|
||||
} from '../HIR/visitors';
|
||||
import {IdentifierState} from './AnalyseFunctions';
|
||||
|
||||
/*
|
||||
* This pass infers which of the given function's context (free) variables
|
||||
@@ -61,24 +61,24 @@ export function inferMutableContextVariables(fn: HIRFunction): void {
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
switch (instr.value.kind) {
|
||||
case "PropertyLoad": {
|
||||
case 'PropertyLoad': {
|
||||
state.declareProperty(
|
||||
instr.lvalue,
|
||||
instr.value.object,
|
||||
instr.value.property
|
||||
instr.value.property,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "ComputedLoad": {
|
||||
case 'ComputedLoad': {
|
||||
/*
|
||||
* The path is set to an empty string as the path doesn't really
|
||||
* matter for a computed load.
|
||||
*/
|
||||
state.declareProperty(instr.lvalue, instr.value.object, "");
|
||||
state.declareProperty(instr.lvalue, instr.value.object, '');
|
||||
break;
|
||||
}
|
||||
case "LoadLocal":
|
||||
case "LoadContext": {
|
||||
case 'LoadLocal':
|
||||
case 'LoadContext': {
|
||||
if (instr.lvalue.identifier.name === null) {
|
||||
state.declareTemporary(instr.lvalue, instr.value.place);
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export function inferMutableContextVariables(fn: HIRFunction): void {
|
||||
function visitOperand(
|
||||
state: IdentifierState,
|
||||
knownMutatedIdentifiers: Set<Identifier>,
|
||||
operand: Place
|
||||
operand: Place,
|
||||
): void {
|
||||
const resolved = state.resolve(operand.identifier);
|
||||
if (operand.effect === Effect.Mutate || operand.effect === Effect.Store) {
|
||||
|
||||
+12
-12
@@ -13,14 +13,14 @@ import {
|
||||
InstructionKind,
|
||||
makeInstructionId,
|
||||
Place,
|
||||
} from "../HIR/HIR";
|
||||
import { printPlace } from "../HIR/PrintHIR";
|
||||
} from '../HIR/HIR';
|
||||
import {printPlace} from '../HIR/PrintHIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionOperand,
|
||||
eachTerminalOperand,
|
||||
} from "../HIR/visitors";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
} from '../HIR/visitors';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/*
|
||||
* For each usage of a value in the given function, determines if the usage
|
||||
@@ -72,7 +72,7 @@ function infer(place: Place, instrId: InstructionId): void {
|
||||
function inferPlace(
|
||||
place: Place,
|
||||
instrId: InstructionId,
|
||||
inferMutableRangeForStores: boolean
|
||||
inferMutableRangeForStores: boolean,
|
||||
): void {
|
||||
switch (place.effect) {
|
||||
case Effect.Unknown: {
|
||||
@@ -99,7 +99,7 @@ function inferPlace(
|
||||
|
||||
export function inferMutableLifetimes(
|
||||
func: HIRFunction,
|
||||
inferMutableRangeForStores: boolean
|
||||
inferMutableRangeForStores: boolean,
|
||||
): void {
|
||||
/*
|
||||
* Context variables only appear to mutate where they are assigned, but we need
|
||||
@@ -125,7 +125,7 @@ export function inferMutableLifetimes(
|
||||
phi.id.mutableRange.start = operand.mutableRange.start;
|
||||
} else {
|
||||
phi.id.mutableRange.start = makeInstructionId(
|
||||
Math.min(phi.id.mutableRange.start, operand.mutableRange.start)
|
||||
Math.min(phi.id.mutableRange.start, operand.mutableRange.start),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -153,23 +153,23 @@ export function inferMutableLifetimes(
|
||||
}
|
||||
|
||||
if (
|
||||
instr.value.kind === "DeclareContext" ||
|
||||
(instr.value.kind === "StoreContext" &&
|
||||
instr.value.kind === 'DeclareContext' ||
|
||||
(instr.value.kind === 'StoreContext' &&
|
||||
instr.value.lvalue.kind !== InstructionKind.Reassign)
|
||||
) {
|
||||
// Save declarations of context variables
|
||||
contextVariableDeclarationInstructions.set(
|
||||
instr.value.lvalue.place.identifier,
|
||||
instr.id
|
||||
instr.id,
|
||||
);
|
||||
} else if (instr.value.kind === "StoreContext") {
|
||||
} else if (instr.value.kind === 'StoreContext') {
|
||||
/*
|
||||
* Else this is a reassignment, extend the range from the declaration (if present).
|
||||
* Note that declarations may not be present for context variables that are reassigned
|
||||
* within a function expression before (or without) a read of the same variable
|
||||
*/
|
||||
const declaration = contextVariableDeclarationInstructions.get(
|
||||
instr.value.lvalue.place.identifier
|
||||
instr.value.lvalue.place.identifier,
|
||||
);
|
||||
if (declaration != null) {
|
||||
const range = instr.value.lvalue.place.identifier.mutableRange;
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { HIRFunction, Identifier } from "../HIR/HIR";
|
||||
import { inferAliases } from "./InferAlias";
|
||||
import { inferAliasForPhis } from "./InferAliasForPhis";
|
||||
import { inferAliasForStores } from "./InferAliasForStores";
|
||||
import { inferMutableLifetimes } from "./InferMutableLifetimes";
|
||||
import { inferMutableRangesForAlias } from "./InferMutableRangesForAlias";
|
||||
import { inferTryCatchAliases } from "./InferTryCatchAliases";
|
||||
import {HIRFunction, Identifier} from '../HIR/HIR';
|
||||
import {inferAliases} from './InferAlias';
|
||||
import {inferAliasForPhis} from './InferAliasForPhis';
|
||||
import {inferAliasForStores} from './InferAliasForStores';
|
||||
import {inferMutableLifetimes} from './InferMutableLifetimes';
|
||||
import {inferMutableRangesForAlias} from './InferMutableRangesForAlias';
|
||||
import {inferTryCatchAliases} from './InferTryCatchAliases';
|
||||
|
||||
export function inferMutableRanges(ir: HIRFunction): void {
|
||||
// Infer mutable ranges for non fields
|
||||
|
||||
+4
-4
@@ -5,12 +5,12 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { HIRFunction, Identifier, InstructionId } from "../HIR/HIR";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import {HIRFunction, Identifier, InstructionId} from '../HIR/HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
export function inferMutableRangesForAlias(
|
||||
_fn: HIRFunction,
|
||||
aliases: DisjointSet<Identifier>
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
const aliasSets = aliases.buildSets();
|
||||
for (const aliasSet of aliasSets) {
|
||||
@@ -19,7 +19,7 @@ export function inferMutableRangesForAlias(
|
||||
* mutated.
|
||||
*/
|
||||
const mutatingIdentifiers = [...aliasSet].filter(
|
||||
(id) => id.mutableRange.end - id.mutableRange.start > 1
|
||||
id => id.mutableRange.end - id.mutableRange.start > 1,
|
||||
);
|
||||
|
||||
if (mutatingIdentifiers.length > 0) {
|
||||
|
||||
+24
-24
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { CompilerError } from "..";
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
BlockId,
|
||||
Effect,
|
||||
@@ -17,19 +17,19 @@ import {
|
||||
getHookKind,
|
||||
isStableType,
|
||||
isUseOperator,
|
||||
} from "../HIR";
|
||||
import { PostDominator } from "../HIR/Dominator";
|
||||
} from '../HIR';
|
||||
import {PostDominator} from '../HIR/Dominator';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from "../HIR/visitors";
|
||||
} from '../HIR/visitors';
|
||||
import {
|
||||
findDisjointMutableValues,
|
||||
isMutable,
|
||||
} from "../ReactiveScopes/InferReactiveScopeVariables";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import { assertExhaustive } from "../Utils/utils";
|
||||
} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
/*
|
||||
* Infers which `Place`s are reactive, ie may *semantically* change
|
||||
@@ -112,7 +112,7 @@ import { assertExhaustive } from "../Utils/utils";
|
||||
export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
const reactiveIdentifiers = new ReactivityMap(findDisjointMutableValues(fn));
|
||||
for (const param of fn.params) {
|
||||
const place = param.kind === "Identifier" ? param : param.place;
|
||||
const place = param.kind === 'Identifier' ? param : param.place;
|
||||
reactiveIdentifiers.markReactive(place);
|
||||
}
|
||||
|
||||
@@ -130,14 +130,14 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
for (const blockId of controlBlocks) {
|
||||
const controlBlock = fn.body.blocks.get(blockId)!;
|
||||
switch (controlBlock.terminal.kind) {
|
||||
case "if":
|
||||
case "branch": {
|
||||
case 'if':
|
||||
case 'branch': {
|
||||
if (reactiveIdentifiers.isReactive(controlBlock.terminal.test)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "switch": {
|
||||
case 'switch': {
|
||||
if (reactiveIdentifiers.isReactive(controlBlock.terminal.test)) {
|
||||
return true;
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
for (const instruction of block.instructions) {
|
||||
const { value } = instruction;
|
||||
const {value} = instruction;
|
||||
let hasReactiveInput = false;
|
||||
/*
|
||||
* NOTE: we want to mark all operands as reactive or not, so we
|
||||
@@ -204,13 +204,13 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
* but we are conservative and assume that the value could be reactive.
|
||||
*/
|
||||
if (
|
||||
value.kind === "CallExpression" &&
|
||||
value.kind === 'CallExpression' &&
|
||||
(getHookKind(fn.env, value.callee.identifier) != null ||
|
||||
isUseOperator(value.callee.identifier))
|
||||
) {
|
||||
hasReactiveInput = true;
|
||||
} else if (
|
||||
value.kind === "MethodCall" &&
|
||||
value.kind === 'MethodCall' &&
|
||||
(getHookKind(fn.env, value.property.identifier) != null ||
|
||||
isUseOperator(value.property.identifier))
|
||||
) {
|
||||
@@ -248,7 +248,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
}
|
||||
case Effect.Unknown: {
|
||||
CompilerError.invariant(false, {
|
||||
reason: "Unexpected unknown effect",
|
||||
reason: 'Unexpected unknown effect',
|
||||
description: null,
|
||||
loc: operand.loc,
|
||||
suggestions: null,
|
||||
@@ -257,7 +257,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
default: {
|
||||
assertExhaustive(
|
||||
operand.effect,
|
||||
`Unexpected effect kind \`${operand.effect}\``
|
||||
`Unexpected effect kind \`${operand.effect}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -265,25 +265,25 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
switch (value.kind) {
|
||||
case "LoadLocal": {
|
||||
case 'LoadLocal': {
|
||||
identifierMapping.set(
|
||||
instruction.lvalue.identifier,
|
||||
value.place.identifier
|
||||
value.place.identifier,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "PropertyLoad":
|
||||
case "ComputedLoad": {
|
||||
case 'PropertyLoad':
|
||||
case 'ComputedLoad': {
|
||||
const resolvedId =
|
||||
identifierMapping.get(value.object.identifier) ??
|
||||
value.object.identifier;
|
||||
identifierMapping.set(instruction.lvalue.identifier, resolvedId);
|
||||
break;
|
||||
}
|
||||
case "LoadContext": {
|
||||
case 'LoadContext': {
|
||||
identifierMapping.set(
|
||||
instruction.lvalue.identifier,
|
||||
value.place.identifier
|
||||
value.place.identifier,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -304,7 +304,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
|
||||
function postDominatorFrontier(
|
||||
fn: HIRFunction,
|
||||
postDominators: PostDominator<BlockId>,
|
||||
targetId: BlockId
|
||||
targetId: BlockId,
|
||||
): Set<BlockId> {
|
||||
const visited = new Set<BlockId>();
|
||||
const frontier = new Set<BlockId>();
|
||||
@@ -328,7 +328,7 @@ function postDominatorFrontier(
|
||||
function postDominatorsOf(
|
||||
fn: HIRFunction,
|
||||
postDominators: PostDominator<BlockId>,
|
||||
targetId: BlockId
|
||||
targetId: BlockId,
|
||||
): Set<BlockId> {
|
||||
const result = new Set<BlockId>();
|
||||
const visited = new Set<BlockId>();
|
||||
|
||||
+203
-203
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { BlockId, HIRFunction, Identifier } from "../HIR";
|
||||
import DisjointSet from "../Utils/DisjointSet";
|
||||
import {BlockId, HIRFunction, Identifier} from '../HIR';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
|
||||
/*
|
||||
* Any values created within a try/catch block could be aliased to the try handler.
|
||||
@@ -16,19 +16,19 @@ import DisjointSet from "../Utils/DisjointSet";
|
||||
*/
|
||||
export function inferTryCatchAliases(
|
||||
fn: HIRFunction,
|
||||
aliases: DisjointSet<Identifier>
|
||||
aliases: DisjointSet<Identifier>,
|
||||
): void {
|
||||
const handlerParams: Map<BlockId, Identifier> = new Map();
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
if (
|
||||
block.terminal.kind === "try" &&
|
||||
block.terminal.kind === 'try' &&
|
||||
block.terminal.handlerBinding !== null
|
||||
) {
|
||||
handlerParams.set(
|
||||
block.terminal.handler,
|
||||
block.terminal.handlerBinding.identifier
|
||||
block.terminal.handlerBinding.identifier,
|
||||
);
|
||||
} else if (block.terminal.kind === "maybe-throw") {
|
||||
} else if (block.terminal.kind === 'maybe-throw') {
|
||||
const handlerParam = handlerParams.get(block.terminal.handler);
|
||||
if (handlerParam === undefined) {
|
||||
/*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user