Compare commits

..

1 Commits

Author SHA1 Message Date
Rick Hanlon
6e33440a44 wip fire 2024-11-04 16:54:52 -05:00
2617 changed files with 261762 additions and 85036 deletions

View File

@@ -8,9 +8,11 @@ indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0

View File

@@ -303,6 +303,7 @@ module.exports = {
ERROR,
{isProductionUserAppCode: true},
],
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
'react-internal/warning-args': ERROR,
'react-internal/no-production-logging': ERROR,
},
@@ -329,18 +330,17 @@ module.exports = {
'packages/react-server-dom-esm/**/*.js',
'packages/react-server-dom-webpack/**/*.js',
'packages/react-server-dom-turbopack/**/*.js',
'packages/react-server-dom-parcel/**/*.js',
'packages/react-server-dom-fb/**/*.js',
'packages/react-test-renderer/**/*.js',
'packages/react-debug-tools/**/*.js',
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-timeline/**/*.js',
'packages/react-native-renderer/**/*.js',
'packages/eslint-plugin-react-hooks/**/*.js',
'packages/jest-react/**/*.js',
'packages/internal-test-utils/**/*.js',
'packages/**/__tests__/*.js',
'packages/**/npm/*.js',
'compiler/packages/eslint-plugin-react-hooks/**/*.js',
],
rules: {
'react-internal/prod-error-codes': OFF,
@@ -446,7 +446,10 @@ module.exports = {
},
},
{
files: ['scripts/eslint-rules/*.js'],
files: [
'scripts/eslint-rules/*.js',
'packages/eslint-plugin-react-hooks/src/*.js',
],
plugins: ['eslint-plugin'],
rules: {
'eslint-plugin/prefer-object-rule': ERROR,
@@ -478,12 +481,6 @@ module.exports = {
__turbopack_require__: 'readonly',
},
},
{
files: ['packages/react-server-dom-parcel/**/*.js'],
globals: {
parcelRequire: 'readonly',
},
},
{
files: ['packages/scheduler/**/*.js'],
globals: {
@@ -493,11 +490,9 @@ module.exports = {
{
files: [
'packages/react-devtools-extensions/**/*.js',
'packages/react-devtools-shared/src/devtools/views/**/*.js',
'packages/react-devtools-shared/src/hook.js',
'packages/react-devtools-shared/src/backend/console.js',
'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
'packages/react-devtools-shared/src/frontend/utils/withPermissionsCheck.js',
],
globals: {
__IS_CHROME__: 'readonly',
@@ -505,7 +500,6 @@ module.exports = {
__IS_EDGE__: 'readonly',
__IS_NATIVE__: 'readonly',
__IS_INTERNAL_VERSION__: 'readonly',
chrome: 'readonly',
},
},
{
@@ -514,26 +508,6 @@ module.exports = {
__IS_INTERNAL_VERSION__: 'readonly',
},
},
{
files: ['compiler/packages/eslint-plugin-react-hooks/src/**/*'],
extends: ['plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin'],
rules: {
'@typescript-eslint/no-explicit-any': OFF,
'@typescript-eslint/no-non-null-assertion': OFF,
'@typescript-eslint/array-type': [ERROR, {default: 'generic'}],
'es/no-optional-chaining': OFF,
'eslint-plugin/prefer-object-rule': ERROR,
'eslint-plugin/require-meta-fixable': [
ERROR,
{catchNoFixerButFixableProperty: true},
],
'eslint-plugin/require-meta-has-suggestions': ERROR,
},
},
],
env: {
@@ -609,12 +583,6 @@ module.exports = {
WheelEventHandler: 'readonly',
FinalizationRegistry: 'readonly',
Omit: 'readonly',
Keyframe: 'readonly',
PropertyIndexedKeyframes: 'readonly',
KeyframeAnimationOptions: 'readonly',
GetAnimationsOptions: 'readonly',
Animatable: 'readonly',
ScrollTimeline: 'readonly',
spyOnDev: 'readonly',
spyOnDevAndProd: 'readonly',

View File

@@ -1,30 +0,0 @@
name: (Compiler) Discord Notify
on:
pull_request_target:
types: [opened, ready_for_review]
paths:
- compiler/**
- .github/workflows/compiler_**.yml
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
with:
actor: ${{ github.event.pull_request.user.login }}
notify:
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
needs: check_maintainer
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.COMPILER_DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}
embed-author-url: ${{ github.event.pull_request.user.html_url }}
embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
embed-description: ${{ github.event.pull_request.body }}
embed-url: ${{ github.event.pull_request.html_url }}

View File

@@ -6,10 +6,10 @@ on:
pull_request:
paths:
- compiler/**
- .github/workflows/compiler_playground.yml
- .github/workflows/compiler-playground.yml
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
@@ -36,17 +36,17 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- name: yarn install compiler
run: yarn install --frozen-lockfile
working-directory: compiler
- name: yarn install playground
run: yarn install --frozen-lockfile
- run: npx playwright install --with-deps chromium
- run: CI=true yarn test
- run: ls -R test-results
if: '!cancelled()'
- run: yarn test
- name: Archive test results
if: '!cancelled()'
uses: actions/upload-artifact@v4
with:
name: test-results
path: compiler/apps/playground/test-results
path: test-results

View File

@@ -46,9 +46,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Publish packages to npm
run: |

77
.github/workflows/compiler_rust.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: (Compiler) Rust
on:
push:
branches: ["main"]
paths:
- .github/workflows/**
- compiler/crates/**
- compiler/Cargo.*
- compiler/*.toml
pull_request:
paths:
- .github/workflows/**
- compiler/crates/**
- compiler/Cargo.*
- compiler/*.toml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
TZ: /usr/share/zoneinfo/America/Los_Angeles
defaults:
run:
working-directory: compiler
jobs:
test:
name: Rust Test (${{ matrix.target.os }})
strategy:
matrix:
target:
- target: ubuntu-latest
os: ubuntu-latest
# TODO: run on more platforms
# - target: macos-latest
# os: macos-latest
# - target: windows-latest
# os: windows-latest
runs-on: ${{ matrix.target.os }}
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: cargo test
run: cargo test --manifest-path=Cargo.toml --locked ${{ matrix.target.features && '--features' }} ${{ matrix.target.features }}
lint:
name: Rust Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
# NOTE: use `rustup run <toolchain> <command>` in commands below
# with this exact same toolchain value
toolchain: nightly-2023-08-01
override: true
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: rustfmt
run: grep -r --include "*.rs" --files-without-match "@generated" crates | xargs rustup run nightly-2023-08-01 rustfmt --check --config="skip_children=true"
# - name: cargo clippy
# run: rustup run nightly-2023-08-01 cargo clippy -- -Dclippy::correctness
build:
name: Rust Build
runs-on: ubuntu-latest
# TODO: build on more platforms, deploy, etc
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: cargo build
run: cargo build --release

View File

@@ -9,7 +9,7 @@ on:
- .github/workflows/compiler_typescript.yml
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
@@ -46,9 +46,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler lint
@@ -67,9 +66,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn workspace babel-plugin-react-compiler jest
@@ -77,8 +75,8 @@ jobs:
name: Test ${{ matrix.workspace_name }}
needs: discover_yarn_workspaces
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
workspace_name: ${{ fromJSON(needs.discover_yarn_workspaces.outputs.matrix) }}
steps:
@@ -92,11 +90,7 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
path: "**/node_modules"
key: compiler-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('compiler/**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: xvfb-run -a yarn workspace ${{ matrix.workspace_name }} test
if: runner.os == 'Linux' && matrix.workspace_name == 'react-forgive'
- run: yarn workspace ${{ matrix.workspace_name }} test
if: matrix.workspace_name != 'react-forgive'

View File

@@ -29,13 +29,11 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release 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
@@ -63,11 +61,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore archived build
uses: actions/download-artifact@v4
@@ -101,7 +96,6 @@ jobs:
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version:
- "16.0"
@@ -110,6 +104,7 @@ jobs:
- "17.0"
- "18.0"
- "18.2" # compiler polyfill
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -121,9 +116,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4
@@ -137,7 +131,6 @@ jobs:
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version:
- "16.0"
@@ -145,6 +138,7 @@ jobs:
- "16.8" # hooks
- "17.0"
- "18.0"
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -156,9 +150,8 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- name: Restore all archived build artifacts
uses: actions/download-artifact@v4

View File

@@ -8,7 +8,7 @@ on:
- compiler/**
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
@@ -17,75 +17,6 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
# ----- NODE_MODULES CACHE -----
# Centralize the node_modules cache so it is saved once and each subsequent job only needs to
# restore the cache. Prevents race conditions where multiple workflows try to write to the cache.
runtime_node_modules_cache:
name: Cache Runtime node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
runtime_compiler_node_modules_cache:
name: Cache Runtime, Compiler node_modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check cache hit
uses: actions/cache/restore@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
if: steps.node_modules.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# ----- FLOW -----
discover_flow_inline_configs:
name: Discover flow inline configs
@@ -94,8 +25,6 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.result }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/github-script@v7
id: set-matrix
with:
@@ -105,59 +34,46 @@ jobs:
flow:
name: Flow check ${{ matrix.flow_inline_config_shortname }}
needs: [discover_flow_inline_configs, runtime_node_modules_cache]
needs: discover_flow_inline_configs
runs-on: ubuntu-latest
continue-on-error: true
strategy:
fail-fast: false
matrix:
flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: node ./scripts/tasks/flow-ci ${{ matrix.flow_inline_config_shortname }}
# ----- FIZZ -----
check_generated_fizz_runtime:
name: Confirm generated inline Fizz runtime is up to date
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: |
yarn generate-inline-fizz-runtime
git diff --quiet || (echo "There was a change to the Fizz runtime. Run `yarn generate-inline-fizz-runtime` and check in the result." && false)
@@ -165,37 +81,28 @@ jobs:
# ----- FEATURE FLAGS -----
flags:
name: Check flags
needs: [runtime_node_modules_cache]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn flags
# ----- TESTS -----
test:
name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }})
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
params:
- "-r=stable --env=development"
@@ -223,72 +130,51 @@ jobs:
- 3/5
- 4/5
- 5/5
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
needs: [runtime_compiler_node_modules_cache]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# yml is dumb. update the --total arg to yarn build if you change the number of workers
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]
worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]
release_channel: [stable, experimental]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11.0.22
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn build --index=${{ matrix.worker_id }} --total=25 --r=${{ matrix.release_channel }} --ci
- run: yarn build --index=${{ matrix.worker_id }} --total=20 --r=${{ matrix.release_channel }} --ci
env:
CI: github
RELEASE_CHANNEL: ${{ matrix.release_channel }}
@@ -305,9 +191,8 @@ jobs:
test_build:
name: yarn test-build
needs: [build_and_lint, runtime_compiler_node_modules_cache]
needs: build_and_lint
strategy:
fail-fast: false
matrix:
test_params: [
# Intentionally passing these as strings instead of creating a
@@ -343,31 +228,22 @@ jobs:
- 1/3
- 2/3
- 3/3
continue-on-error: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -380,28 +256,22 @@ jobs:
process_artifacts_combined:
name: Process artifacts combined
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -410,7 +280,7 @@ jobs:
merge-multiple: true
- name: Display structure of build
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- run: echo ${{ github.sha }} >> build/COMMIT_SHA
- name: Scrape warning messages
run: |
mkdir -p ./build/__test_utils__
@@ -429,28 +299,22 @@ jobs:
check_error_codes:
name: Search build artifacts for unminified errors
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -466,28 +330,22 @@ jobs:
check_release_dependencies:
name: Check release dependencies
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -504,24 +362,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_dom-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn --cwd fixtures/dom install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
path: "**/node_modules"
key: fixtures_dom-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
working-directory: fixtures/dom
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -545,8 +399,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
@@ -556,16 +408,12 @@ jobs:
# That means dependencies of the built packages are not installed.
# We need to install dependencies of the workroot to fulfill all dependency constraints
- name: Restore cached node_modules
uses: actions/cache@v4 # note: this does not reuse centralized cache since it has unique cache key
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: fixtures_flight-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: fixtures_flight-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -583,7 +431,9 @@ jobs:
fi
- name: Playwright install deps
working-directory: fixtures/flight
run: npx playwright install --with-deps chromium
run: |
npx playwright install
sudo npx playwright install-deps
- name: Run tests
working-directory: fixtures/flight
run: yarn test
@@ -604,85 +454,68 @@ jobs:
# ----- DEVTOOLS -----
build_devtools_and_process_artifacts:
name: Build DevTools and process artifacts
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_and_lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chrome, firefox, edge]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
pattern: _build_*
path: build
merge-multiple: true
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh ${{ matrix.browser }}
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
env:
RELEASE_CHANNEL: experimental
- name: Display structure of build
run: ls -R build
# Simplifies getting the extension for local testing
- name: Archive ${{ matrix.browser }} extension
- name: Archive devtools build
uses: actions/upload-artifact@v4
with:
name: react-devtools-${{ matrix.browser }}-extension
path: build/devtools/${{ matrix.browser }}-extension.zip
merge_devtools_artifacts:
name: Merge DevTools artifacts
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
steps:
- name: Merge artifacts
uses: actions/upload-artifact/merge@v4
with:
name: react-devtools
pattern: react-devtools-*-extension
path: build/devtools.tgz
# Simplifies getting the extension for local testing
- name: Archive chrome extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-chrome-extension
path: build/devtools/chrome-extension.zip
- name: Archive firefox extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-firefox-extension
path: build/devtools/firefox-extension.zip
run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: [build_and_lint, runtime_node_modules_cache]
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build
uses: actions/download-artifact@v4
with:
@@ -697,36 +530,30 @@ jobs:
RELEASE_CHANNEL: experimental
# ----- SIZEBOT -----
sizebot:
if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }}
name: Run sizebot
needs: [build_and_lint, runtime_node_modules_cache]
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
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache/restore@v4
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }})
git fetch origin main
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse origin/main)
mv ./build ./base-build
# TODO: The `download-experimental-build` script copies the npm
# packages into the `node_modules` directory. This is a historical
@@ -734,12 +561,32 @@ jobs:
# don't exist.
- name: Delete extraneous files
run: rm -rf ./base-build/node_modules
- name: Display structure of base-build from origin/main
- name: Display structure of base-build
run: ls -R base-build
- name: Ensure clean build directory
run: rm -rf build
- name: Archive base-build
uses: actions/upload-artifact@v4
with:
name: base-build
path: base-build
sizebot:
name: Run sizebot
needs: [build_and_lint, download_base_build_for_sizebot]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: "**/node_modules"
key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Restore archived build for PR
uses: actions/download-artifact@v4
with:
@@ -752,7 +599,14 @@ jobs:
node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js
- name: Display structure of build for PR
run: ls -R build
- run: echo ${{ github.event.pull_request.head.sha || github.sha }} >> build/COMMIT_SHA
- 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

View File

@@ -16,11 +16,6 @@ on:
required: true
default: false
type: boolean
dry_run:
description: Perform a dry run (run everything except push)
required: true
default: false
type: boolean
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
@@ -78,13 +73,13 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
name: yarn install (react)
- run: yarn install --frozen-lockfile
name: yarn install (scripts/release)
working-directory: scripts/release
- name: Download artifacts for base revision
run: |
GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}
@@ -110,10 +105,9 @@ jobs:
mkdir ./compiled/facebook-www/__test_utils__
mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
# Move eslint-plugin-react-hooks into eslint-plugin-react-hooks
mkdir ./compiled/eslint-plugin-react-hooks
# Move eslint-plugin-react-hooks into facebook-www
mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
./compiled/eslint-plugin-react-hooks/index.js
./compiled/facebook-www/eslint-plugin-react-hooks.js
# Move unstable_server-external-runtime.js into facebook-www
mv build/oss-experimental/react-dom/unstable_server-external-runtime.js \
@@ -246,22 +240,18 @@ jobs:
run: |
echo ":"
git status -u
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-www
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true
commit_fbsource_artifacts:
needs: download_artifacts
@@ -417,19 +407,15 @@ jobs:
run: |
git add .
git status
- name: Check commit message
if: inputs.dry_run
run: |
git fetch origin --quiet
git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:"%B"
- name: Commit changes to branch
if: inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true'
run: |
git config --global user.email "${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}"
git config --global user.name "${{ github.triggering_actor }}"
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: |
${{ github.event.workflow_run.head_commit.message || format('Manual build of {0}', github.event.workflow_run.head_sha || github.sha) }}
git fetch origin --quiet
git commit -m "$(git show ${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }} --no-patch --pretty=format:'%B%n%nDiffTrain build for [${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ inputs.commit_sha || github.event.workflow_run.head_sha || github.sha}})')" || echo "No changes to commit"
- name: Push changes to branch
if: inputs.dry_run == false && (inputs.force == true || steps.check_should_commit.outputs.should_commit == 'true')
run: git push
DiffTrain build for [${{ github.event.workflow_run.head_sha || github.sha }}](https://github.com/facebook/react/commit/${{ github.event.workflow_run.head_sha || github.sha }})
branch: builds/facebook-fbsource
commit_user_name: ${{ github.triggering_actor }}
commit_user_email: ${{ format('{0}@users.noreply.github.com', github.triggering_actor) }}
create_branch: true

View File

@@ -1,30 +0,0 @@
name: (Runtime) Discord Notify
on:
pull_request_target:
types: [opened, ready_for_review]
paths-ignore:
- compiler/**
- .github/workflows/compiler_**.yml
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
with:
actor: ${{ github.event.pull_request.user.login }}
notify:
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
needs: check_maintainer
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.pull_request.user.login }}
embed-author-url: ${{ github.event.pull_request.user.html_url }}
embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }}
embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}'
embed-description: ${{ github.event.pull_request.body }}
embed-url: ${{ github.event.pull_request.html_url }}

View File

@@ -1,60 +0,0 @@
name: (Runtime) ESLint Plugin E2E
on:
push:
branches: [main]
pull_request:
paths-ignore:
- compiler/**
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
# ----- TESTS -----
test:
name: ESLint v${{ matrix.eslint_major }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
eslint_major:
- "6"
- "7"
- "8"
- "9"
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: |
yarn.lock
compiler/yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-and-compiler-eslint_e2e-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd compiler install --frozen-lockfile
- name: Build plugin
working-directory: fixtures/eslint-v${{ matrix.eslint_major }}
run: node build.mjs
- name: Install fixture dependencies
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn --frozen-lockfile
- name: Run lint test
working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }}
run: yarn lint

View File

@@ -39,13 +39,11 @@ jobs:
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: yarn install --frozen-lockfile
working-directory: scripts/release
- run: |
scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc

View File

@@ -27,7 +27,7 @@ jobs:
# because this used to be called the "next" channel and some
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
dist_tag: canary,next
dist_tag: canary,next,rc
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -15,7 +15,7 @@ jobs:
with:
commit_sha: ${{ github.sha }}
release_channel: stable
dist_tag: canary,next
dist_tag: canary,next,rc
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,125 +0,0 @@
name: (Runtime) Publish Releases from NPM Manual
on:
workflow_dispatch:
inputs:
version_to_promote:
required: true
description: Current npm version (non-experimental) to promote
type: string
version_to_publish:
required: true
description: Version to publish for the specified packages
type: string
only_packages:
description: Packages to publish (space separated)
type: string
skip_packages:
description: Packages to NOT publish (space separated)
type: string
tags:
description: NPM tags (space separated)
type: string
default: untagged
dry:
required: true
description: Dry run instead of publish?
type: boolean
default: true
force_notify:
description: Force a Discord notification?
type: boolean
default: false
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
GH_TOKEN: ${{ github.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
notify:
if: ${{ inputs.force_notify || inputs.dry == false || inputs.dry == 'false' }}
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.sender.login }}
embed-author-url: ${{ github.event.sender.html_url }}
embed-author-icon-url: ${{ github.event.sender.avatar_url }}
embed-title: "⚠️ Publishing release from NPM${{ (inputs.dry && ' (dry run)') || '' }}"
embed-description: |
```json
${{ toJson(inputs) }}
```
embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }}
publish:
name: Publish releases
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: yarn
cache-dependency-path: yarn.lock
- name: Restore cached node_modules
uses: actions/cache@v4
id: node_modules
with:
path: |
**/node_modules
key: runtime-release-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
- run: yarn --cwd scripts/release install --frozen-lockfile
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- if: '${{ inputs.only_packages }}'
name: 'Prepare ${{ inputs.only_packages }} from NPM'
run: |
scripts/release/prepare-release-from-npm.js \
--ci \
--skipTests \
--version=${{ inputs.version_to_promote }} \
--publishVersion=${{ inputs.version_to_publish }} \
--onlyPackages=${{ inputs.only_packages }}
- if: '${{ inputs.skip_packages }}'
name: 'Prepare all packages EXCEPT ${{ inputs.skip_packages }} from NPM'
run: |
scripts/release/prepare-release-from-npm.js \
--ci \
--skipTests \
--version=${{ inputs.version_to_promote }} \
--publishVersion=${{ inputs.version_to_publish }} \
--skipPackages=${{ inputs.skip_packages }}
- name: Check prepared files
run: ls -R build/node_modules
- if: '${{ inputs.only_packages }}'
name: 'Publish ${{ inputs.only_packages }}'
run: |
scripts/release/publish.js \
--ci \
--tags=${{ inputs.tags }} \
--publishVersion=${{ inputs.version_to_publish }} \
--onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry'}}
- if: '${{ inputs.skip_packages }}'
name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
run: |
scripts/release/publish.js \
--ci \
--tags=${{ inputs.tags }} \
--publishVersion=${{ inputs.version_to_publish }} \
--skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry'}}
- name: Archive released package for debugging
uses: actions/upload-artifact@v4
with:
name: build
path: |
./build/node_modules

View File

@@ -1,71 +0,0 @@
name: (Shared) Check maintainer
on:
workflow_call:
inputs:
actor:
required: true
type: string
is_remote:
required: false
type: boolean
default: false
outputs:
is_core_team:
value: ${{ jobs.check_maintainer.outputs.is_core_team }}
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
check_maintainer:
runs-on: ubuntu-latest
outputs:
is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }}
steps:
- uses: actions/checkout@v4
- name: Check if actor is maintainer
id: check_if_actor_is_maintainer
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const actor = '${{ inputs.actor }}';
let isRemote = ${{ inputs.is_remote }};
if (typeof isRemote === 'string') {
isRemote = isRemote === 'true';
}
if (typeof isRemote !== 'boolean') {
throw new Error(`Invalid \`isRemote\` input. Expected a boolean, got: ${isRemote}`);
}
let content = null;
if (isRemote === true) {
const res = await github.rest.repos.getContent({
owner: 'facebook',
repo: 'react',
path: 'MAINTAINERS',
ref: 'main',
headers: { Accept: 'application/vnd.github+json' }
});
if (res.status !== 200) {
console.error(res);
throw new Error('Unable to fetch MAINTAINERS file');
}
content = Buffer.from(res.data.content, 'base64').toString();
} else {
content = await fs.readFileSync('./MAINTAINERS', { encoding: 'utf8' });
}
if (content === null) {
throw new Error('Unable to retrieve local or http MAINTAINERS file');
}
const maintainers = new Set(content.split('\n'));
if (maintainers.has(actor)) {
console.log(`🟢 ${actor} is a maintainer`);
return true;
}
console.log(`🔴 ${actor} is NOT a maintainer`);
return null;

View File

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

View File

@@ -1,37 +0,0 @@
name: (Shared) Close Direct Sync Branch PRs
on:
pull_request:
branches:
- 'builds/facebook-**'
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
close_pr:
runs-on: ubuntu-latest
steps:
- name: Close PR
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pullNumber = ${{ github.event.number }};
await github.rest.pulls.createReview({
owner,
repo,
pull_number: pullNumber,
body: 'Do not land changes to `${{ github.event.pull_request.base.ref }}`. Please re-open your PR targeting `main` instead.',
event: 'REQUEST_CHANGES'
});
await github.rest.pulls.update({
owner,
repo,
pull_number: pullNumber,
state: 'closed'
});

View File

@@ -1,31 +0,0 @@
name: (Shared) Label Core Team PRs
on:
pull_request_target:
env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
# https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
jobs:
check_maintainer:
uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main
with:
actor: ${{ github.event.pull_request.user.login }}
label:
if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }}
runs-on: ubuntu-latest
needs: check_maintainer
steps:
- name: Label PR as React Core Team
uses: actions/github-script@v7
with:
script: |
github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ${{ github.event.number }},
labels: ['React Core Team']
});

View File

@@ -6,7 +6,7 @@ on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
@@ -28,11 +28,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: yarn prettier-check
@@ -49,11 +46,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: node ./scripts/tasks/eslint
@@ -70,11 +64,8 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: ./scripts/ci/check_license.sh
@@ -91,10 +82,7 @@ jobs:
- name: Restore cached node_modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
path: "**/node_modules"
key: shared-lint-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- run: yarn install --frozen-lockfile
- run: ./scripts/ci/test_print_warnings.sh

1
.gitignore vendored
View File

@@ -37,4 +37,3 @@ packages/react-devtools-fusebox/dist
packages/react-devtools-inline/dist
packages/react-devtools-shell/dist
packages/react-devtools-timeline/dist

View File

@@ -1,275 +1,3 @@
## 19.0.0 (December 5, 2024)
Below is a list of all new features, APIs, deprecations, and breaking changes. Read [React 19 release post](https://react.dev/blog/2024/04/25/react-19) and [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for more information.
> Note: To help make the upgrade to React 19 easier, weve published a react@18.3 release that is identical to 18.2 but adds warnings for deprecated APIs and other changes that are needed for React 19. We recommend upgrading to React 18.3.1 first to help identify any issues before upgrading to React 19.
### New Features
#### React
* Actions: `startTransition` can now accept async functions. Functions passed to `startTransition` are called “Actions”. A given Transition can include one or more Actions which update state in the background and update the UI with one commit. In addition to updating state, Actions can now perform side effects including async requests, and the Action will wait for the work to finish before finishing the Transition. This feature allows Transitions to include side effects like `fetch()` in the pending state, and provides support for error handling, and optimistic updates.
* `useActionState`: is a new hook to order Actions inside of a Transition with access to the state of the action, and the pending state. It accepts a reducer that can call Actions, and the initial state used for first render. It also accepts an optional string that is used if the action is passed to a form `action` prop to support progressive enhancement in forms.
* `useOptimistic`: is a new hook to update state while a Transition is in progress. It returns the state, and a set function that can be called inside a transition to “optimistically” update the state to expected final value immediately while the Transition completes in the background. When the transition finishes, the state is updated to the new value.
* `use`: is a new API that allows reading resources in render. In React 19, `use` accepts a promise or Context. If provided a promise, `use` will suspend until a value is resolved. `use` can only be used in render but can be called conditionally.
* `ref` as a prop: Refs can now be used as props, removing the need for `forwardRef`.
* **Suspense sibling pre-warming**: When a component suspends, React will immediately commit the fallback of the nearest Suspense boundary, without waiting for the entire sibling tree to render. After the fallback commits, React will schedule another render for the suspended siblings to “pre-warm” lazy requests.
#### React DOM Client
* `<form> action` prop: Form Actions allow you to manage forms automatically and integrate with `useFormStatus`. When a `<form> action` succeeds, React will automatically reset the form for uncontrolled components. The form can be reset manually with the new `requestFormReset` API.
* `<button> and <input> formAction` prop: Actions can be passed to the `formAction` prop to configure form submission behavior. This allows using different Actions depending on the input.
* `useFormStatus`: is a new hook that provides the status of the parent `<form> action`, as if the form was a Context provider. The hook returns the values: `pending`, `data`, `method`, and `action`.
* Support for Document Metadata: Weve added support for rendering document metadata tags in components natively. React will automatically hoist them into the `<head>` section of the document.
* Support for Stylesheets: React 19 will ensure stylesheets are inserted into the `<head>` on the client before revealing the content of a Suspense boundary that depends on that stylesheet.
* Support for async scripts: Async scripts can be rendered anywhere in the component tree and React will handle ordering and deduplication.
* Support for preloading resources: React 19 ships with `preinit`, `preload`, `prefetchDNS`, and `preconnect` APIs to optimize initial page loads by moving discovery of additional resources like fonts out of stylesheet loading. They can also be used to prefetch resources used by an anticipated navigation.
#### React DOM Server
* Added `prerender` and `prerenderToNodeStream` APIs for static site generation. They are designed to work with streaming environments like Node.js Streams and Web Streams. Unlike `renderToString`, they wait for data to load for HTML generation.
#### React Server Components
* RSC features such as directives, server components, and server functions are now stable. This means libraries that ship with Server Components can now target React 19 as a peer dependency with a react-server export condition for use in frameworks that support the Full-stack React Architecture. The underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. See [docs](https://19.react.dev/reference/rsc/server-components) for how to support React Server Components.
### Deprecations
* Deprecated: `element.ref` access: React 19 supports ref as a prop, so were deprecating `element.ref` in favor of `element.props.ref`. Accessing will result in a warning.
* `react-test-renderer`: In React 19, react-test-renderer logs a deprecation warning and has switched to concurrent rendering for web usage. We recommend migrating your tests to [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://testing-library.com/docs/react-native-testing-library/intro)
### Breaking Changes
React 19 brings in a number of breaking changes, including the removals of long-deprecated APIs. We recommend first upgrading to `18.3.1`, where we've added additional deprecation warnings. Check out the [upgrade guide](https://19.react.dev/blog/2024/04/25/react-19-upgrade-guide) for more details and guidance on codemodding.
### React
* New JSX Transform is now required: We introduced [a new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) in 2020 to improve bundle size and use JSX without importing React. In React 19, were adding additional improvements like using ref as a prop and JSX speed improvements that require the new transform.
* Errors in render are not re-thrown: Errors that are not caught by an Error Boundary are now reported to window.reportError. Errors that are caught by an Error Boundary are reported to console.error. Weve introduced `onUncaughtError` and `onCaughtError` methods to `createRoot` and `hydrateRoot` to customize this error handling.
* Removed: `propTypes`: Using `propTypes` will now be silently ignored. If required, we recommend migrating to TypeScript or another type-checking solution.
* Removed: `defaultProps` for functions: ES6 default parameters can be used in place. Class components continue to support `defaultProps` since there is no ES6 alternative.
* Removed: `contextTypes` and `getChildContext`: Legacy Context for class components has been removed in favor of the `contextType` API.
* Removed: string refs: Any usage of string refs need to be migrated to ref callbacks.
* Removed: Module pattern factories: A rarely used pattern that can be migrated to regular functions.
* Removed: `React.createFactory`: Now that JSX is broadly supported, all `createFactory` usage can be migrated to JSX components.
* Removed: `react-test-renderer/shallow`: This has been a re-export of [react-shallow-renderer](https://github.com/enzymejs/react-shallow-renderer) since React 18\. If needed, you can continue to use the third-party package directly. We recommend using [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://testing-library.com/docs/react-native-testing-library/intro) instead.
#### React DOM
* Removed: `react-dom/test-utils`: Weve moved `act` from `react-dom/test-utils` to react. All other utilities have been removed.
* Removed: `ReactDOM`.`render`, `ReactDOM`.`hydrate`: These have been removed in favor of the concurrent equivalents: `ReactDOM`.`createRoot` and `ReactDOM.hydrateRoot`.
* Removed: `unmountComponentAtNode`: Removed in favor of `root.unmount()`.
* Removed: `ReactDOM`.`findDOMNode`: You can replace `ReactDOM`.`findDOMNode` with DOM Refs.
### Notable Changes
#### React
* `<Context>` as a provider: You can now render `<Context>` as a provider instead of `<Context.Provider>`.
* Cleanup functions for refs: When the component unmounts, React will call the cleanup function returned from the ref callback.
* `useDeferredValue` initial value argument: When provided, `useDeferredValue` will return the initial value for the initial render of a component, then schedule a re-render in the background with the `deferredValue` returned.
* Support for Custom Elements: React 19 now passes all tests on [Custom Elements Everywhere](https://custom-elements-everywhere.com/).
* StrictMode changes: `useMemo` and `useCallback` will now reuse the memoized results from the first render, during the second render. Additionally, StrictMode will now double-invoke ref callback functions on initial mount.
* UMD builds removed: To load React 19 with a script tag, we recommend using an ESM-based CDN such as [esm.sh](http://esm.sh).
#### React DOM
* Diffs for hydration errors: In the case of a mismatch, React 19 logs a single error with a diff of the mismatched content.
* Compatibility with third-party scripts and extensions: React will now force a client re-render to fix up any mismatched content caused by elements inserted by third-party JS.
### TypeScript Changes
The most common changes can be codemodded with `npx types-react-codemod@latest preset-19 ./path-to-your-react-ts-files`.
* Removed deprecated TypeScript types:
* `ReactChild` (replacement: `React.ReactElement | number | string)`
* `ReactFragment` (replacement: `Iterable<React.ReactNode>`)
* `ReactNodeArray` (replacement: `ReadonlyArray<React.ReactNode>`)
* `ReactText` (replacement: `number | string`)
* `VoidFunctionComponent` (replacement: `FunctionComponent`)
* `VFC` (replacement: `FC`)
* Moved to `prop-types`: `Requireable`, `ValidationMap`, `Validator`, `WeakValidationMap`
* Moved to `create-react-class`: `ClassicComponentClass`, `ClassicComponent`, `ClassicElement`, `ComponentSpec`, `Mixin`, `ReactChildren`, `ReactHTML`, `ReactSVG`, `SFCFactory`
* Disallow implicit return in refs: refs can now accept cleanup functions. When you return something else, we cant tell if you intentionally returned something not meant to clean up or returned the wrong value. Implicit returns of anything but functions will now error.
* Require initial argument to `useRef`: The initial argument is now required to match `useState`, `createContext` etc
* Refs are mutable by default: Ref objects returned from `useRef()` are now always mutable instead of sometimes being immutable. This feature was too confusing for users and conflicted with legit cases where refs were managed by React and manually written to.
* Strict `ReactElement` typing: The props of React elements now default to `unknown` instead of `any` if the element is typed as `ReactElement`
* JSX namespace in TypeScript: The global `JSX` namespace is removed to improve interoperability with other libraries using JSX. Instead, the JSX namespace is available from the React package: `import { JSX } from 'react'`
* Better `useReducer` typings: Most `useReducer` usage should not require explicit type arguments.
For example,
```diff
-useReducer<React.Reducer<State, Action>>(reducer)
+useReducer(reducer)
```
or
```diff
-useReducer<React.Reducer<State, Action>>(reducer)
+useReducer<State, Action>(reducer)
```
### All Changes
#### React
* Add support for async Actions ([\#26621](https://github.com/facebook/react/pull/26621), [\#26726](https://github.com/facebook/react/pull/26726), [\#28078](https://github.com/facebook/react/pull/28078), [\#28097](https://github.com/facebook/react/pull/28097), [\#29226](https://github.com/facebook/react/pull/29226), [\#29618](https://github.com/facebook/react/pull/29618), [\#29670](https://github.com/facebook/react/pull/29670), [\#26716](https://github.com/facebook/react/pull/26716) by [@acdlite](https://github.com/acdlite) and [@sebmarkbage](https://github.com/sebmarkbage))
* Add `useActionState()` hook to update state based on the result of a Form Action ([\#27270](https://github.com/facebook/react/pull/27270), [\#27278](https://github.com/facebook/react/pull/27278), [\#27309](https://github.com/facebook/react/pull/27309), [\#27302](https://github.com/facebook/react/pull/27302), [\#27307](https://github.com/facebook/react/pull/27307), [\#27366](https://github.com/facebook/react/pull/27366), [\#27370](https://github.com/facebook/react/pull/27370), [\#27321](https://github.com/facebook/react/pull/27321), [\#27374](https://github.com/facebook/react/pull/27374), [\#27372](https://github.com/facebook/react/pull/27372), [\#27397](https://github.com/facebook/react/pull/27397), [\#27399](https://github.com/facebook/react/pull/27399), [\#27460](https://github.com/facebook/react/pull/27460), [\#28557](https://github.com/facebook/react/pull/28557), [\#27570](https://github.com/facebook/react/pull/27570), [\#27571](https://github.com/facebook/react/pull/27571), [\#28631](https://github.com/facebook/react/pull/28631), [\#28788](https://github.com/facebook/react/pull/28788), [\#29694](https://github.com/facebook/react/pull/29694), [\#29695](https://github.com/facebook/react/pull/29695), [\#29694](https://github.com/facebook/react/pull/29694), [\#29665](https://github.com/facebook/react/pull/29665), [\#28232](https://github.com/facebook/react/pull/28232), [\#28319](https://github.com/facebook/react/pull/28319) by [@acdlite](https://github.com/acdlite), [@eps1lon](https://github.com/eps1lon), and [@rickhanlonii](https://github.com/rickhanlonii))
* Add `use()` API to read resources in render ([\#25084](https://github.com/facebook/react/pull/25084), [\#25202](https://github.com/facebook/react/pull/25202), [\#25207](https://github.com/facebook/react/pull/25207), [\#25214](https://github.com/facebook/react/pull/25214), [\#25226](https://github.com/facebook/react/pull/25226), [\#25247](https://github.com/facebook/react/pull/25247), [\#25539](https://github.com/facebook/react/pull/25539), [\#25538](https://github.com/facebook/react/pull/25538), [\#25537](https://github.com/facebook/react/pull/25537), [\#25543](https://github.com/facebook/react/pull/25543), [\#25561](https://github.com/facebook/react/pull/25561), [\#25620](https://github.com/facebook/react/pull/25620), [\#25615](https://github.com/facebook/react/pull/25615), [\#25922](https://github.com/facebook/react/pull/25922), [\#25641](https://github.com/facebook/react/pull/25641), [\#25634](https://github.com/facebook/react/pull/25634), [\#26232](https://github.com/facebook/react/pull/26232), [\#26536](https://github.com/facebook/react/pull/26535), [\#26739](https://github.com/facebook/react/pull/26739), [\#28233](https://github.com/facebook/react/pull/28233) by [@acdlite](https://github.com/acdlite), [@MofeiZ](https://github.com/mofeiZ), [@sebmarkbage](https://github.com/sebmarkbage), [@sophiebits](https://github.com/sophiebits), [@eps1lon](https://github.com/eps1lon), and [@hansottowirtz](https://github.com/hansottowirtz))
* Add `useOptimistic()` hook to display mutated state optimistically during an async mutation ([\#26740](https://github.com/facebook/react/pull/26740), [\#26772](https://github.com/facebook/react/pull/26772), [\#27277](https://github.com/facebook/react/pull/27277), [\#27453](https://github.com/facebook/react/pull/27453), [\#27454](https://github.com/facebook/react/pull/27454), [\#27936](https://github.com/facebook/react/pull/27936) by [@acdlite](https://github.com/acdlite))
* Added an `initialValue` argument to `useDeferredValue()` hook ([\#27500](https://github.com/facebook/react/pull/27500), [\#27509](https://github.com/facebook/react/pull/27509), [\#27512](https://github.com/facebook/react/pull/27512), [\#27888](https://github.com/facebook/react/pull/27888), [\#27550](https://github.com/facebook/react/pull/27550) by [@acdlite](https://github.com/acdlite))
* Support refs as props, warn on `element.ref` access ([\#28348](https://github.com/facebook/react/pull/28348), [\#28464](https://github.com/facebook/react/pull/28464), [\#28731](https://github.com/facebook/react/pull/28731) by [@acdlite](https://github.com/acdlite))
* Support Custom Elements ([\#22184](https://github.com/facebook/react/pull/22184), [\#26524](https://github.com/facebook/react/pull/26524), [\#26523](https://github.com/facebook/react/pull/26523), [\#27511](https://github.com/facebook/react/pull/27511), [\#24541](https://github.com/facebook/react/pull/24541) by [@josepharhar](https://github.com/josepharhar), [@sebmarkbage](https://github.com/sebmarkbage), [@gnoff](https://github.com/gnoff) and [@eps1lon](https://github.com/eps1lon))
* Add ref cleanup function ([\#25686](https://github.com/facebook/react/pull/25686), [\#28883](https://github.com/facebook/react/pull/28883), [\#28910](https://github.com/facebook/react/pull/28910) by [@sammy-SC](https://github.com/sammy-SC), [@jackpope](https://github.com/jackpope), and [@kassens](https://github.com/kassens))
* Sibling pre-rendering replaced by sibling pre-warming ([\#26380](https://github.com/facebook/react/pull/26380), [\#26549](https://github.com/facebook/react/pull/26549), [\#30761](https://github.com/facebook/react/pull/30761), [\#30800](https://github.com/facebook/react/pull/30800), [\#30762](https://github.com/facebook/react/pull/30762), [\#30879](https://github.com/facebook/react/pull/30879), [\#30934](https://github.com/facebook/react/pull/30934), [\#30952](https://github.com/facebook/react/pull/30952), [\#31056](https://github.com/facebook/react/pull/31056), [\#31452](https://github.com/facebook/react/pull/31452) by [@sammy-SC](https://github.com/sammy-SC), [@acdlite](https://github.com/acdlite), [@gnoff](https://github.com/gnoff), [@jackpope](https://github.com/jackpope), [@rickhanlonii](https://github.com/rickhanlonii))
* Dont rethrow errors at the root ([\#28627](https://github.com/facebook/react/pull/28627), [\#28641](https://github.com/facebook/react/pull/28641) by [@sebmarkbage](https://github.com/sebmarkbage))
* Batch sync discrete, continuous, and default lanes ([\#25700](https://github.com/facebook/react/pull/25700) by [@tyao1](https://github.com/tyao1))
* Switch `<Context>` to mean `<Context.Provider>` ([\#28226](https://github.com/facebook/react/pull/28226) by [@gaearon](https://github.com/gaearon))
* Changes to *StrictMode*
* Handle `info`, `group`, and `groupCollapsed` in *StrictMode* logging ([\#25172](https://github.com/facebook/react/pull/25172) by [@timneutkens](https://github.com/timneutkens))
* Refs are now attached/detached/attached in *StrictMode* ([\#25049](https://github.com/facebook/react/pull/25049) by [@sammy-SC](https://github.com/sammy-SC))
* Fix `useSyncExternalStore()` hydration in *StrictMode* ([\#26791](https://github.com/facebook/react/pull/26791) by [@sophiebits](https://github.com/sophiebits))
* Always trigger `componentWillUnmount()` in *StrictMode* ([\#26842](https://github.com/facebook/react/pull/26842) by [@tyao1](https://github.com/tyao1))
* Restore double invoking `useState()` and `useReducer()` initializer functions in *StrictMode* ([\#28248](https://github.com/facebook/react/pull/28248) by [@eps1lon](https://github.com/eps1lon))
* Reuse memoized result from first pass ([\#25583](https://github.com/facebook/react/pull/25583) by [@acdlite](https://github.com/acdlite))
* Fix `useId()` in *StrictMode* ([\#25713](https://github.com/facebook/react/pull/25713) by [@gnoff](https://github.com/gnoff))
* Add component name to *StrictMode* error messages ([\#25718](https://github.com/facebook/react/pull/25718) by [@sammy-SC](https://github.com/sammy-SC))
* Add support for rendering BigInt ([\#24580](https://github.com/facebook/react/pull/24580) by [@eps1lon](https://github.com/eps1lon))
* `act()` no longer checks `shouldYield` which can be inaccurate in test environments ([\#26317](https://github.com/facebook/react/pull/26317) by [@acdlite](https://github.com/acdlite))
* Warn when keys are spread with props ([\#25697](https://github.com/facebook/react/pull/25697), [\#26080](https://github.com/facebook/react/pull/26080) by [@sebmarkbage](https://github.com/sebmarkbage) and [@kassens](https://github.com/kassens))
* Generate sourcemaps for production build artifacts ([\#26446](https://github.com/facebook/react/pull/26446) by [@markerikson](https://github.com/markerikson))
* Improve stack diffing algorithm ([\#27132](https://github.com/facebook/react/pull/27132) by [@KarimP](https://github.com/KarimP))
* Suspense throttling lowered from 500ms to 300ms ([\#26803](https://github.com/facebook/react/pull/26803) by [@acdlite](https://github.com/acdlite))
* Lazily propagate context changes ([\#20890](https://github.com/facebook/react/pull/20890) by [@acdlite](https://github.com/acdlite) and [@gnoff](https://github.com/gnoff))
* Immediately rerender pinged fiber ([\#25074](https://github.com/facebook/react/pull/25074) by [@acdlite](https://github.com/acdlite))
* Move update scheduling to microtask ([\#26512](https://github.com/facebook/react/pull/26512) by [@acdlite](https://github.com/acdlite))
* Consistently apply throttled retries ([\#26611](https://github.com/facebook/react/pull/26611), [\#26802](https://github.com/facebook/react/pull/26802) by [@acdlite](https://github.com/acdlite))
* Suspend Thenable/Lazy if it's used in React.Children ([\#28284](https://github.com/facebook/react/pull/28284) by [@sebmarkbage](https://github.com/sebmarkbage))
* Detect infinite update loops caused by render phase updates ([\#26625](https://github.com/facebook/react/pull/26625) by [@acdlite](https://github.com/acdlite))
* Update conditional hooks warning ([\#29626](https://github.com/facebook/react/pull/29626) by [@sophiebits](https://github.com/sophiebits))
* Update error URLs to go to new docs ([\#27240](https://github.com/facebook/react/pull/27240) by [@rickhanlonii](https://github.com/rickhanlonii))
* Rename the `react.element` symbol to `react.transitional.element` ([\#28813](https://github.com/facebook/react/pull/28813) by [@sebmarkbage](https://github.com/sebmarkbage))
* Fix crash when suspending in shell during `useSyncExternalStore()` re-render ([\#27199](https://github.com/facebook/react/pull/27199) by [@acdlite](https://github.com/acdlite))
* Fix incorrect “detected multiple renderers" error in tests ([\#22797](https://github.com/facebook/react/pull/22797) by [@eps1lon](https://github.com/eps1lon))
* Fix bug where effect cleanup may be called twice after bailout ([\#26561](https://github.com/facebook/react/pull/26561) by [@acdlite](https://github.com/acdlite))
* Fix suspending in shell during discrete update ([\#25495](https://github.com/facebook/react/pull/25495) by [@acdlite](https://github.com/acdlite))
* Fix memory leak after repeated setState bailouts ([\#25309](https://github.com/facebook/react/pull/25309) by [@acdlite](https://github.com/acdlite))
* Fix `useSyncExternalStore()` dropped update when state is dispatched in render phase ([\#25578](https://github.com/facebook/react/pull/25578) by [@pandaiolo](https://github.com/pandaiolo))
* Fix logging when rendering a lazy fragment ([\#30372](https://github.com/facebook/react/pull/30372) by [@tom-sherman](https://github.com/tom-sherman))
* Remove string refs ([\#25383](https://github.com/facebook/react/pull/25383), [\#28322](https://github.com/facebook/react/pull/28322) by [@eps1lon](https://github.com/eps1lon) and [@acdlite](https://github.com/acdlite))
* Remove Legacy Context (\#30319 by [@kassens](https://github.com/kassens))
* Remove `RefreshRuntime.findAffectedHostInstances` ([\#30538](https://github.com/facebook/react/pull/30538) by [@gaearon](https://github.com/gaearon))
* Remove client caching from `cache()` API ([\#27977](https://github.com/facebook/react/pull/27977), [\#28250](https://github.com/facebook/react/pull/28250) by [@acdlite](https://github.com/acdlite) and [@gnoff](https://github.com/gnoff))
* Remove `propTypes` ([\#28324](https://github.com/facebook/react/pull/28324), [\#28326](https://github.com/facebook/react/pull/28326) by [@gaearon](https://github.com/gaearon))
* Remove `defaultProps` support, except for classes ([\#28733](https://github.com/facebook/react/pull/28733) by [@acdlite](https://github.com/acdlite))
* Remove UMD builds ([\#28735](https://github.com/facebook/react/pull/28735) by [@gnoff](https://github.com/gnoff))
* Remove delay for non-transition updates ([\#26597](https://github.com/facebook/react/pull/26597) by [@acdlite](https://github.com/acdlite))
* Remove `createFactory` ([\#27798](https://github.com/facebook/react/pull/27798) by [@kassens](https://github.com/kassens))
#### React DOM
* Adds Form Actions to handle form submission ([\#26379](https://github.com/facebook/react/pull/26379), [\#26674](https://github.com/facebook/react/pull/26674), [\#26689](https://github.com/facebook/react/pull/26689), [\#26708](https://github.com/facebook/react/pull/26708), [\#26714](https://github.com/facebook/react/pull/26714), [\#26735](https://github.com/facebook/react/pull/26735), [\#26846](https://github.com/facebook/react/pull/26846), [\#27358](https://github.com/facebook/react/pull/27358), [\#28056](https://github.com/facebook/react/pull/28056) by [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), and [@jupapios](https://github.com/jupapios))
* Add `useFormStatus()` hook to provide status information of the last form submission ([\#26719](https://github.com/facebook/react/pull/26719), [\#26722](https://github.com/facebook/react/pull/26722), [\#26788](https://github.com/facebook/react/pull/26788), [\#29019](https://github.com/facebook/react/pull/29019), [\#28728](https://github.com/facebook/react/pull/28728), [\#28413](https://github.com/facebook/react/pull/28413) by [@acdlite](https://github.com/acdlite) and [@eps1lon](https://github.com/eps1lon))
* Support for Document Metadata. Adds `preinit`, `preinitModule`, `preconnect`, `prefetchDNS`, `preload`, and `preloadModule` APIs.
* [\#25060](https://github.com/facebook/react/pull/25060), [\#25243](https://github.com/facebook/react/pull/25243), [\#25388](https://github.com/facebook/react/pull/25388), [\#25432](https://github.com/facebook/react/pull/25432), [\#25436](https://github.com/facebook/react/pull/25436), [\#25426](https://github.com/facebook/react/pull/25426), [\#25500](https://github.com/facebook/react/pull/25500), [\#25480](https://github.com/facebook/react/pull/25480), [\#25508](https://github.com/facebook/react/pull/25508), [\#25515](https://github.com/facebook/react/pull/25515), [\#25514](https://github.com/facebook/react/pull/25514), [\#25532](https://github.com/facebook/react/pull/25532), [\#25536](https://github.com/facebook/react/pull/25536), [\#25534](https://github.com/facebook/react/pull/25534), [\#25546](https://github.com/facebook/react/pull/25546), [\#25559](https://github.com/facebook/react/pull/25559), [\#25569](https://github.com/facebook/react/pull/25569), [\#25599](https://github.com/facebook/react/pull/25599), [\#25689](https://github.com/facebook/react/pull/25689), [\#26106](https://github.com/facebook/react/pull/26106), [\#26152](https://github.com/facebook/react/pull/26152), [\#26239](https://github.com/facebook/react/pull/26239), [\#26237](https://github.com/facebook/react/pull/26237), [\#26280](https://github.com/facebook/react/pull/26280), [\#26154](https://github.com/facebook/react/pull/26154), [\#26256](https://github.com/facebook/react/pull/26256), [\#26353](https://github.com/facebook/react/pull/26353), [\#26427](https://github.com/facebook/react/pull/26427), [\#26450](https://github.com/facebook/react/pull/26450), [\#26502](https://github.com/facebook/react/pull/26502), [\#26514](https://github.com/facebook/react/pull/26514), [\#26531](https://github.com/facebook/react/pull/26531), [\#26532](https://github.com/facebook/react/pull/26532), [\#26557](https://github.com/facebook/react/pull/26557), [\#26871](https://github.com/facebook/react/pull/26871), [\#26881](https://github.com/facebook/react/pull/26881), [\#26877](https://github.com/facebook/react/pull/26877), [\#26873](https://github.com/facebook/react/pull/26873), [\#26880](https://github.com/facebook/react/pull/26880), [\#26942](https://github.com/facebook/react/pull/26942), [\#26938](https://github.com/facebook/react/pull/26938), [\#26940](https://github.com/facebook/react/pull/26940), [\#26939](https://github.com/facebook/react/pull/26939), [\#27030](https://github.com/facebook/react/pull/27030), [\#27201](https://github.com/facebook/react/pull/27201), [\#27212](https://github.com/facebook/react/pull/27212), [\#27217](https://github.com/facebook/react/pull/27217), [\#27218](https://github.com/facebook/react/pull/27218), [\#27220](https://github.com/facebook/react/pull/27220), [\#27224](https://github.com/facebook/react/pull/27224), [\#27223](https://github.com/facebook/react/pull/27223), [\#27269](https://github.com/facebook/react/pull/27269), [\#27260](https://github.com/facebook/react/pull/27260), [\#27347](https://github.com/facebook/react/pull/27347), [\#27346](https://github.com/facebook/react/pull/27346), [\#27361](https://github.com/facebook/react/pull/27361), [\#27400](https://github.com/facebook/react/pull/27400), [\#27541](https://github.com/facebook/react/pull/27541), [\#27610](https://github.com/facebook/react/pull/27610), [\#28110](https://github.com/facebook/react/pull/28110), [\#29693](https://github.com/facebook/react/pull/29693), [\#29732](https://github.com/facebook/react/pull/29732), [\#29811](https://github.com/facebook/react/pull/29811), [\#27586](https://github.com/facebook/react/pull/27586), [\#28069](https://github.com/facebook/react/pull/28069) by [@gnoff](https://github.com/gnoff), [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), [@kassens](https://github.com/kassens), [@sokra](https://github.com/sokra), [@sweetliquid](https://github.com/sweetliquid)
* Add `fetchPriority` to `<img>` and `<link>` ([\#25927](https://github.com/facebook/react/pull/25927) by [@styfle](https://github.com/styfle))
* Add support for SVG `transformOrigin` prop ([\#26130](https://github.com/facebook/react/pull/26130) by [@arav-ind](https://github.com/arav-ind))
* Add support for `onScrollEnd` event ([\#26789](https://github.com/facebook/react/pull/26789) by [@devongovett](https://github.com/devongovett))
* Allow `<hr>` as child of `<select>` ([\#27632](https://github.com/facebook/react/pull/27632) by [@SouSingh](https://github.com/SouSingh))
* Add support for Popover API ([\#27981](https://github.com/facebook/react/pull/27981) by [@eps1lon](https://github.com/eps1lon))
* Add support for `inert` ([\#24730](https://github.com/facebook/react/pull/24730) by [@eps1lon](https://github.com/eps1lon))
* Add support for `imageSizes` and `imageSrcSet` ([\#22550](https://github.com/facebook/react/pull/22550) by [@eps1lon](https://github.com/eps1lon))
* Synchronously flush transitions in popstate events ([\#26025](https://github.com/facebook/react/pull/26025), [\#27559](https://github.com/facebook/react/pull/27559), [\#27505](https://github.com/facebook/react/pull/27505), [\#30759](https://github.com/facebook/react/pull/30759) by [@tyao1](https://github.com/tyao1) and [@acdlite](https://github.com/acdlite))
* `flushSync` exhausts queue even if something throws ([\#26366](https://github.com/facebook/react/pull/26366) by [@acdlite](https://github.com/acdlite))
* Throw error if `react` and `react-dom` versions dont match ([\#29236](https://github.com/facebook/react/pull/29236) by [@acdlite](https://github.com/acdlite))
* Ensure `srcset` and `src` are assigned last on `<img>` instances ([\#30340](https://github.com/facebook/react/pull/30340) by [@gnoff](https://github.com/gnoff))
* Javascript URLs are replaced with functions that throw errors ([\#26507](https://github.com/facebook/react/pull/26507), [\#29808](https://github.com/facebook/react/pull/29808) by [@sebmarkbage](https://github.com/sebmarkbage) and [@kassens](https://github.com/kassens))
* Treat toggle and beforetoggle as discrete events ([\#29176](https://github.com/facebook/react/pull/29176) by [@eps1lon](https://github.com/eps1lon))
* Filter out empty `src` and `href` attributes (unless for `<a href=”” />`) ([\#18513](https://github.com/facebook/react/pull/18513), [\#28124](https://github.com/facebook/react/pull/28124) by [@bvaughn](https://github.com/bvaughn) and [@eps1lon](https://github.com/eps1lon))
* Fix unitless `scale` style property ([\#25601](https://github.com/facebook/react/pull/25601) by [@JonnyBurger](https://github.com/JonnyBurger))
* Fix `onChange` error message for controlled `<select>` ([\#27740](https://github.com/facebook/react/pull/27740) by [@Biki-das](https://github.com/Biki-das))
* Fix focus restore in child windows after element reorder ([\#30951](https://github.com/facebook/react/pull/30951) by [@ling1726](https://github.com/ling1726))
* Remove `render`, `hydrate`, `findDOMNode`, `unmountComponentAtNode`, `unstable_createEventHandle`, `unstable_renderSubtreeIntoContainer`, and `unstable_runWithPriority`. Move `createRoot` and `hydrateRoot` to `react-dom/client`. ([\#28271](https://github.com/facebook/react/pull/28271) by [@gnoff](https://github.com/gnoff))
* Remove `test-utils` ([\#28541](https://github.com/facebook/react/pull/28541) by [@eps1lon](https://github.com/eps1lon))
* Remove `unstable_flushControlled` ([\#26397](https://github.com/facebook/react/pull/26397) by [@kassens](https://github.com/kassens))
* Remove legacy mode ([\#28468](https://github.com/facebook/react/pull/28468) by [@gnoff](https://github.com/gnoff))
* Remove `renderToStaticNodeStream()` ([\#28873](https://github.com/facebook/react/pull/28873) by @gnoff)
* Remove `unstable_renderSubtreeIntoContainer` ([\#29771](https://github.com/facebook/react/pull/29771) by [@kassens](https://github.com/kassens))
#### React DOM Server
* Stable release of React Server Components ([Many, many PRs](https://github.com/facebook/react/pulls?q=is%3Apr+is%3Aclosed+%5BFlight%5D+in%3Atitle+created%3A%3C2024-12-01+) by [@sebmarkbage](https://github.com/sebmarkbage), [@acdlite](https://github.com/acdlite), [@gnoff](https://github.com/gnoff), [@sammy-SC](https://github.com/sammy-SC), [@gaearon](https://github.com/gaearon), [@sophiebits](https://github.com/sophiebits), [@unstubbable](https://github.com/unstubbable), [@lubieowoce](https://github.com/lubieowoce))
* Support Server Actions ([\#26124](https://github.com/facebook/react/pull/26124), [\#26632](https://github.com/facebook/react/pull/26632), [\#27459](https://github.com/facebook/react/pull/27459) by [@sebmarkbage](https://github.com/sebmarkbage) and [@acdlite](https://github.com/acdlite))
* Changes to SSR
* Add external runtime which bootstraps hydration on the client for binary transparency ([\#25437](https://github.com/facebook/react/pull/25437), [\#26169](https://github.com/facebook/react/pull/26169), [\#25499](https://github.com/facebook/react/pull/25499) by [@MofeiZ](https://github.com/mofeiZ) and [@acdlite](https://github.com/acdlite))
* Support subresource integrity for `bootstrapScripts` and `bootstrapModules` ([\#25104](https://github.com/facebook/react/pull/25104) by [@gnoff](https://github.com/gnoff))
* Fix null bytes written at text chunk boundaries ([\#26228](https://github.com/facebook/react/pull/26228) by [@sophiebits](https://github.com/sophiebits))
* Fix logic around attribute serialization ([\#26526](https://github.com/facebook/react/pull/26526) by [@gnoff](https://github.com/gnoff))
* Fix precomputed chunk cleared on Node 18 ([\#25645](https://github.com/facebook/react/pull/25645) by [@feedthejim](https://github.com/feedthejim))
* Optimize end tag chunks ([\#27522](https://github.com/facebook/react/pull/27522) by [@yujunjung](https://github.com/yujunjung))
* Gracefully handle suspending in DOM configs ([\#26768](https://github.com/facebook/react/pull/26768) by [@sebmarkbage](https://github.com/sebmarkbage))
* Check for nullish values on ReactCustomFormAction ([\#26770](https://github.com/facebook/react/pull/26770) by [@sebmarkbage](https://github.com/sebmarkbage))
* Preload `bootstrapModules`, `bootstrapScripts`, and update priority queue ([\#26754](https://github.com/facebook/react/pull/26754), [\#26753](https://github.com/facebook/react/pull/26753), [\#27190](https://github.com/facebook/react/pull/27190), [\#27189](https://github.com/facebook/react/pull/27189) by [@gnoff](https://github.com/gnoff))
* Client render the nearest child or parent suspense boundary if replay errors or is aborted ([\#27386](https://github.com/facebook/react/pull/27386) by [@sebmarkbage](https://github.com/sebmarkbage))
* Don't bail out of flushing if we still have pending root tasks ([\#27385](https://github.com/facebook/react/pull/27385) by [@sebmarkbage](https://github.com/sebmarkbage))
* Ensure Resumable State is Serializable ([\#27388](https://github.com/facebook/react/pull/27388) by [@sebmarkbage](https://github.com/sebmarkbage))
* Remove extra render pass when reverting to client render ([\#26445](https://github.com/facebook/react/pull/26445) by [@acdlite](https://github.com/acdlite))
* Fix unwinding context during selective hydration ([\#25876](https://github.com/facebook/react/pull/25876) by [@tyao1](https://github.com/tyao1))
* Stop flowing and then abort if a stream is cancelled ([\#27405](https://github.com/facebook/react/pull/27405) by [@sebmarkbage](https://github.com/sebmarkbage))
* Pass cancellation reason to abort ([\#27536](https://github.com/facebook/react/pull/27536) by [@sebmarkbage](https://github.com/sebmarkbage))
* Add `onHeaders` entrypoint option ([\#27641](https://github.com/facebook/react/pull/27641), [\#27712](https://github.com/facebook/react/pull/27712) by [@gnoff](https://github.com/gnoff))
* Escape `<style>` and `<script>` textContent to enable rendering inner content without dangerouslySetInnerHTML ([\#28870](https://github.com/facebook/react/pull/28870), [\#28871](https://github.com/facebook/react/pull/28871) by [@gnoff](https://github.com/gnoff))
* Fallback to client replaying actions for Blob serialization ([\#28987](https://github.com/facebook/react/pull/28987) by [@sebmarkbage](https://github.com/sebmarkbage))
* Render Suspense fallback if boundary contains new stylesheet during sync update ([\#28965](https://github.com/facebook/react/pull/28965) by [@gnoff](https://github.com/gnoff))
* Fix header length tracking (\#30327 by [@gnoff](https://github.com/gnoff))
* Use `srcset` to trigger load event on mount (\#30351 by [@gnoff](https://github.com/gnoff))
* Don't perform work when closing stream (\#30497 by [@gnoff](https://github.com/gnoff))
* Allow aborting during render (\#30488, [\#30730](https://github.com/facebook/react/pull/30730) by [@gnoff](https://github.com/gnoff))
* Start initial work immediately (\#31079 by [@gnoff](https://github.com/gnoff))
* A transition flowing into a dehydrated boundary no longer suspends when showing fallback ([\#27230](https://github.com/facebook/react/pull/27230) by [@acdlite](https://github.com/acdlite))
* Fix selective hydration triggers false update loop error ([\#27439](https://github.com/facebook/react/pull/27439) by [@acdlite](https://github.com/acdlite))
* Warn for Child Iterator of all types but allow Generator Components ([\#28853](https://github.com/facebook/react/pull/28853) by [@sebmarkbage](https://github.com/sebmarkbage))
* Include regular stack trace in serialized errors ([\#28684](https://github.com/facebook/react/pull/28684), [\#28738](https://github.com/facebook/react/pull/28738) by [@sebmarkbage](https://github.com/sebmarkbage))
* Aborting early no longer infinitely suspends ([\#24751](https://github.com/facebook/react/pull/24751) by [@sebmarkbage](https://github.com/sebmarkbage))
* Fix hydration warning suppression in text comparisons ([\#24784](https://github.com/facebook/react/pull/24784) by [@gnoff](https://github.com/gnoff))
* Changes to error handling in SSR
* Add diffs to hydration warnings ([\#28502](https://github.com/facebook/react/pull/28502), [\#28512](https://github.com/facebook/react/pull/28512) by [@sebmarkbage](https://github.com/sebmarkbage))
* Make Error creation lazy ([\#24728](https://github.com/facebook/react/pull/24728) by [@sebmarkbage](https://github.com/sebmarkbage))
* Remove recoverable error when a sync update flows into a dehydrated boundary ([\#25692](https://github.com/facebook/react/pull/25692) by [@sebmarkbage](https://github.com/sebmarkbage))
* Don't "fix up" mismatched text content with suppressedHydrationWarning ([\#26391](https://github.com/facebook/react/pull/26391) by [@sebmarkbage](https://github.com/sebmarkbage))
* Fix component stacks in errors ([\#27456](https://github.com/facebook/react/pull/27456) by [@sebmarkbage](https://github.com/sebmarkbage))
* Add component stacks to `onError` ([\#27761](https://github.com/facebook/react/pull/27761), [\#27850](https://github.com/facebook/react/pull/27850) by [@gnoff](https://github.com/gnoff) and [@sebmarkbage](https://github.com/sebmarkbage))
* Throw hydration mismatch errors once ([\#28502](https://github.com/facebook/react/pull/28502) by [@sebmarkbage](https://github.com/sebmarkbage))
* Add Bun streaming server renderer ([\#25597](https://github.com/facebook/react/pull/25597) by [@colinhacks](https://github.com/colinhacks))
* Add nonce support to bootstrap scripts ([\#26738](https://github.com/facebook/react/pull/26738) by [@danieltott](https://github.com/danieltott))
* Add `crossorigin` support to bootstrap scripts ([\#26844](https://github.com/facebook/react/pull/26844) by [@HenriqueLimas](https://github.com/HenriqueLimas))
* Support `nonce` and `fetchpriority` in preload links ([\#26826](https://github.com/facebook/react/pull/26826) by [@liuyenwei](https://github.com/liuyenwei))
* Add `referrerPolicy` to `ReactDOM.preload()` ([\#27096](https://github.com/facebook/react/pull/27096) by [@styfle](https://github.com/styfle))
* Add server condition for `react/jsx-dev-runtime` ([\#28921](https://github.com/facebook/react/pull/28921) by [@himself65](https://github.com/himself65))
* Export version ([\#29596](https://github.com/facebook/react/pull/29596) by [@unstubbable](https://github.com/unstubbable))
* Rename the secret export of Client and Server internals ([\#28786](https://github.com/facebook/react/pull/28786), [\#28789](https://github.com/facebook/react/pull/28789) by [@sebmarkbage](https://github.com/sebmarkbage))
* Remove layout effect warning on server ([\#26395](https://github.com/facebook/react/pull/26395) by [@rickhanlonii](https://github.com/rickhanlonii))
* Remove `errorInfo.digest` from `onRecoverableError` ([\#28222](https://github.com/facebook/react/pull/28222) by [@gnoff](https://github.com/gnoff))
#### ReactTestRenderer
* Add deprecation error to `react-test-renderer` on web ([\#27903](https://github.com/facebook/react/pull/27903), [\#28904](https://github.com/facebook/react/pull/28904) by [@jackpope](https://github.com/jackpope) and [@acdlite](https://github.com/acdlite))
* Render with ConcurrentRoot on web ([\#28498](https://github.com/facebook/react/pull/28498) by [@jackpope](https://github.com/jackpope))
* Remove `react-test-renderer/shallow` export ([\#25475](https://github.com/facebook/react/pull/25475), [\#28497](https://github.com/facebook/react/pull/28497) by [@sebmarkbage](https://github.com/sebmarkbage) and [@jackpope](https://github.com/jackpope))
#### React Reconciler
* Enable suspending commits without blocking render ([\#26398](https://github.com/facebook/react/pull/26398), [\#26427](https://github.com/facebook/react/pull/26427) by [@acdlite](https://github.com/acdlite))
* Remove `prepareUpdate` ([\#26583](https://github.com/facebook/react/pull/26583), [\#27409](http://github.com/facebook/react/pull/27409) by [@sebmarkbage](https://github.com/sebmarkbage) and [@sophiebits](https://github.com/sophiebits))
#### React-Is
* Enable tree shaking ([\#27701](https://github.com/facebook/react/pull/27701) by [@markerikson](https://github.com/markerikson))
* Remove `isConcurrentMode` and `isAsyncMode` methods ([\#28224](https://github.com/facebook/react/pull/28224) by @gaearon)
#### useSyncExternalStore
* Remove React internals access ([\#29868](https://github.com/facebook/react/pull/29868) by [@phryneas](https://github.com/phryneas))
* Fix stale selectors keeping previous store references ([\#25969](https://github.com/facebook/react/pull/25968) by [@jellevoost](https://github.com/jellevoost))
## 18.3.1 (April 26, 2024)
- Export `act` from `react` [f1338f](https://github.com/facebook/react/commit/f1338f8080abd1386454a10bbf93d67bfe37ce85)

View File

@@ -1,22 +0,0 @@
acdlite
eps1lon
gaearon
gnoff
unstubbable
hoxyq
jackpope
jbonta
jbrown215
josephsavona
kassens
mattcarrollcode
mofeiZ
mvitousek
pieterv
poteto
rickhanlonii
sebmarkbage
sethwebster
sophiebits
elicwhite
yuzhi

View File

@@ -7,18 +7,18 @@
//
// The @latest channel uses the version as-is, e.g.:
//
// 19.1.0
// 19.0.0
//
// The @canary channel appends additional information, with the scheme
// <version>-<label>-<commit_sha>, e.g.:
//
// 19.1.0-canary-a1c2d3e4
// 19.0.0-canary-a1c2d3e4
//
// The @experimental channel doesn't include a version, only a date and a sha, e.g.:
//
// 0.0.0-experimental-241c4467e-20200129
const ReactVersion = '19.1.0';
const ReactVersion = '19.0.0';
// The label used by the @canary channel. Represents the upcoming release's
// stability. Most of the time, this will be "canary", but we may temporarily
@@ -26,27 +26,27 @@ const ReactVersion = '19.1.0';
//
// It only affects the label used in the version string. To customize the
// npm dist tags used during publish, refer to .github/workflows/runtime_prereleases_*.yml.
const canaryChannelLabel = 'canary';
const canaryChannelLabel = 'rc';
// If the canaryChannelLabel is "rc", the build pipeline will use this to build
// an RC version of the packages.
const rcNumber = 0;
const stablePackages = {
'jest-react': '0.17.0',
'eslint-plugin-react-hooks': '5.1.0',
'jest-react': '0.16.0',
react: ReactVersion,
'react-art': ReactVersion,
'react-dom': ReactVersion,
'react-server-dom-webpack': ReactVersion,
'react-server-dom-turbopack': ReactVersion,
'react-server-dom-parcel': ReactVersion,
'react-is': ReactVersion,
'react-reconciler': '0.32.0',
'react-refresh': '0.17.0',
'react-reconciler': '0.31.0',
'react-refresh': '0.16.0',
'react-test-renderer': ReactVersion,
'use-subscription': '1.11.0',
'use-sync-external-store': '1.5.0',
scheduler: '0.26.0',
'use-subscription': '1.10.0',
'use-sync-external-store': '1.4.0',
scheduler: '0.25.0',
};
// These packages do not exist in the @canary or @latest channel, only

View File

@@ -1,17 +0,0 @@
/**
* This file is purely being used for local jest runs, and doesn't participate in the build process.
*/
'use strict';
module.exports = {
plugins: [
'@babel/plugin-syntax-jsx',
'@babel/plugin-transform-flow-strip-types',
['@babel/plugin-transform-class-properties', {loose: true}],
'@babel/plugin-transform-classes',
],
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};

6
compiler/.gitignore vendored
View File

@@ -21,8 +21,4 @@ dist
.spr.yml
testfilter.txt
bundle-oss.sh
# forgive
*.vsix
.vscode-test
bundle-oss.sh

1217
compiler/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

61
compiler/Cargo.toml Normal file
View File

@@ -0,0 +1,61 @@
[workspace]
resolver = "2"
members = ["crates/*"]
[workspace.package]
authors = ["The React Team https://react.dev/community/team"]
description = "React Compiler"
edition = "2021"
homepage = "https://github.com/facebook/react"
keywords = ["JavaScript", "TypeScript", "React", "React Compiler", "Compiler"]
license = "MIT"
repository = "https://github.com/facebook/react"
[workspace.dependencies]
# workspace crates
react_build_hir = { path = "crates/react_build_hir" }
react_diagnostics = { path = "crates/react_diagnostics" }
react_estree = { path = "crates/react_estree" }
react_estree_codegen = { path = "crates/react_estree_codegen" }
react_fixtures = { path = "crates/react_fixtures" }
react_hermes_parser = { path = "crates/react_hermes_parser" }
react_hir = { path = "crates/react_hir" }
react_optimization = { path = "crates/react_optimization" }
react_semantic_analysis = { path = "crates/react_semantic_analysis" }
react_ssa = { path = "crates/react_ssa" }
react_utils = { path = "crates/react_utils" }
# dependencies
indexmap = { version = "2.0.0", features = ["serde"] }
insta = { version = "1.30.0", features = ["glob"] }
miette = { version = "5.9.0" }
prettyplease = "0.2.10"
quote = "1.0.29"
serde = { version = "1.0.167", features = ["serde_derive"] }
serde_json = "1.0.100"
stacker = "0.1.15"
static_assertions = "1.1.0"
syn = "2.0.23"
thiserror = "1.0.41"
hermes = { git = "https://github.com/facebook/hermes.git" }
juno_support = { git = "https://github.com/facebook/hermes.git" }
[profile.release]
# configuration adapted from oxc
# https://github.com/Boshen/oxc/blob/ea85ee9f2d64dd284c5b7410f491d81fb879abae/Cargo.toml#L89-L97
opt-level = 3
lto = "fat"
codegen-units = 1
strip = "symbols"
debug = false
panic = "abort" # Let it crash and force ourselves to write safe Rust.
# Make insta run faster by compiling with release mode optimizations
# https://docs.rs/insta/latest/insta/#optional-faster-runs
[profile.dev.package.insta]
opt-level = 3
# Make insta diffing libary faster by compiling with release mode optimizations
# https://docs.rs/insta/latest/insta/#optional-faster-runs
[profile.dev.package.similar]
opt-level = 3

View File

@@ -26,13 +26,6 @@ $ npm run dev
$ yarn
```
## Testing
```sh
# Install playwright browser binaries
$ npx playwright install --with-deps
# Run tests
$ yarn test
```
## Deployment
This project has been deployed using Vercel. Vercel does the exact same thing as we would

View File

@@ -1,14 +0,0 @@
import { c as _c } from "react/compiler-runtime";
export default function TestComponent(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -1,12 +0,0 @@
import { c as _c } from "react/compiler-runtime";
export default function MyApp() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <div>Hello World</div>;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}

View File

@@ -1,4 +0,0 @@
// @compilationMode(infer)
function nonReactFn() {
  return {};
}

View File

@@ -1,10 +1,8 @@
import { c as _c } from "react/compiler-runtime"; // 
        @compilationMode(all)
function nonReactFn() {
function MyApp() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = {};
    t0 = <div>Hello World</div>;
    $[0] = t0;
  } else {
    t0 = $[0];

View File

@@ -1,14 +0,0 @@
function TestComponent(t0) {
"use memo";
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -1,15 +0,0 @@
"use memo";
import { c as _c } from "react/compiler-runtime";
export default function TestComponent(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}

View File

@@ -1,4 +0,0 @@
"use no memo";
export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}

View File

@@ -1,14 +0,0 @@
import { c as _c } from "react/compiler-runtime";
function useFoo(propVal) {
  const $ = _c(2);
  const t0 = (propVal.baz: number);
  let t1;
  if ($[0] !== t0) {
    t1 = <div>{t0}</div>;
    $[0] = t0;
    $[1] = t1;
  } else {
    t1 = $[1];
  }
  return t1;
}

View File

@@ -1,20 +0,0 @@
import { c as _c } from "react/compiler-runtime";
function Foo() {
  const $ = _c(2);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = foo();
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  const x = t0 as number;
  let t1;
  if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
    t1 = <div>{x}</div>;
    $[1] = t1;
  } else {
    t1 = $[1];
  }
  return t1;
}

View File

@@ -1,5 +0,0 @@
"use no memo";
function TestComponent({ x }) {
"use memo";
return <Button>{x}</Button>;
}

View File

@@ -1,29 +0,0 @@
import { c as _c } from "react/compiler-runtime";
function TestComponent(t0) {
"use memo";
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
const TestComponent2 = (t0) => {
"use memo";
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = <Button>{x}</Button>;
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
};

View File

@@ -1,8 +0,0 @@
const TestComponent = function () {
"use no memo";
return <Button>{x}</Button>;
};
const TestComponent2 = ({ x }) => {
"use no memo";
return <Button>{x}</Button>;
};

View File

@@ -0,0 +1,13 @@
function TestComponent(t0) {
  const $ = _c(2);
  const { x } = t0;
  let t1;
  if ($[0] !== x) {
    t1 = <Button>{x}</Button>;
    $[0] = x;
    $[1] = t1;
  } else {
    t1 = $[1];
  }
  return t1;
}

View File

@@ -7,191 +7,43 @@
import {expect, test} from '@playwright/test';
import {encodeStore, type Store} from '../../lib/stores';
import {format} from 'prettier';
function isMonacoLoaded(): boolean {
return (
typeof window['MonacoEnvironment'] !== 'undefined' &&
window['__MONACO_LOADED__'] === true
);
}
function formatPrint(data: Array<string>): Promise<string> {
return format(data.join(''), {parser: 'babel'});
}
const TEST_CASE_INPUTS = [
{
name: 'module-scope-use-memo',
input: `
'use memo';
export default function TestComponent({ x }) {
const STORE: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}`,
},
{
name: 'module-scope-use-no-memo',
input: `
'use no memo';
export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}`,
},
{
name: 'use-memo',
input: `
function TestComponent({ x }) {
'use memo';
return <Button>{x}</Button>;
}
const TestComponent2 = ({ x }) => {
'use memo';
return <Button>{x}</Button>;
};`,
},
{
name: 'use-no-memo',
input: `
const TestComponent = function() {
'use no memo';
return <Button>{x}</Button>;
};
const TestComponent2 = ({ x }) => {
'use no memo';
return <Button>{x}</Button>;
};`,
},
{
name: 'todo-function-scope-does-not-beat-module-scope',
input: `
'use no memo';
function TestComponent({ x }) {
'use memo';
return <Button>{x}</Button>;
}`,
},
{
name: 'parse-typescript',
input: `
function Foo() {
const x = foo() as number;
return <div>{x}</div>;
}
`,
noFormat: true,
},
{
name: 'parse-flow',
input: `
// @flow
function useFoo(propVal: {+baz: number}) {
return <div>{(propVal.baz as number)}</div>;
}
`,
noFormat: true,
},
{
name: 'compilationMode-infer',
input: `// @compilationMode(infer)
function nonReactFn() {
return {};
}
`,
noFormat: true,
},
{
name: 'compilationMode-all',
input: `// @compilationMode(all)
function nonReactFn() {
return {};
}
`,
noFormat: true,
},
];
};
const HASH = encodeStore(STORE);
test('editor should open successfully', async ({page}) => {
await page.goto(`/`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
function concat(data: Array<string>): string {
return data.join('');
}
test('editor should compile successfully', async ({page}) => {
await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'});
await page.screenshot({
fullPage: true,
path: 'test-results/00-fresh-page.png',
path: 'test-results/00-on-networkidle.png',
});
});
test('editor should compile from hash successfully', async ({page}) => {
const store: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}
`,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
// User input from hash compiles
await page.screenshot({
fullPage: true,
path: 'test-results/01-compiles-from-hash.png',
path: 'test-results/01-show-js-before.png',
});
const text =
const userInput =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
const output = await formatPrint(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('01-user-output.txt');
});
test('reset button works', async ({page}) => {
const store: Store = {
source: `export default function TestComponent({ x }) {
return <Button>{x}</Button>;
}
`,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
expect(concat(userInput)).toMatchSnapshot('user-input.txt');
// Reset button works
page.on('dialog', dialog => dialog.accept());
await page.getByRole('button', {name: 'Reset'}).click();
await page.screenshot({
fullPage: true,
path: 'test-results/02-reset-button-works.png',
path: 'test-results/02-show-js-after.png',
});
const text =
const defaultInput =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
const output = await formatPrint(text);
expect(output).not.toEqual('');
expect(output).toMatchSnapshot('02-default-output.txt');
expect(concat(defaultInput)).toMatchSnapshot('default-input.txt');
});
TEST_CASE_INPUTS.forEach((t, idx) =>
test(`playground compiles: ${t.name}`, async ({page}) => {
const store: Store = {
source: t.input,
};
const hash = encodeStore(store);
await page.goto(`/#${hash}`, {waitUntil: 'networkidle'});
await page.waitForFunction(isMonacoLoaded);
await page.screenshot({
fullPage: true,
path: `test-results/03-0${idx}-${t.name}.png`,
});
const text =
(await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? [];
let output: string;
if (t.noFormat) {
output = text.join('');
} else {
output = await formatPrint(text);
}
expect(output).not.toEqual('');
expect(output).toMatchSnapshot(`${t.name}-output.txt`);
}),
);

View File

@@ -5,21 +5,21 @@
* LICENSE file in the root directory of this source tree.
*/
import {parse as babelParse, ParseResult} from '@babel/parser';
import {parse as babelParse} from '@babel/parser';
import * as HermesParser from 'hermes-parser';
import traverse, {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import BabelPluginReactCompiler, {
import {
CompilerError,
CompilerErrorDetail,
Effect,
ErrorSeverity,
parseConfigPragmaForTests,
parseConfigPragma,
ValueKind,
runPlayground,
type Hook,
PluginOptions,
CompilerPipelineValue,
parsePluginOptions,
} 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';
@@ -37,18 +37,13 @@ import {useStore, useStoreDispatch} from '../StoreContext';
import Input from './Input';
import {
CompilerOutput,
CompilerTransformOutput,
default as Output,
PrintedCompilerPipelineValue,
} from './Output';
import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
import {transformFromAstSync} from '@babel/core';
function parseInput(
input: string,
language: 'flow' | 'typescript',
): ParseResult<t.File> {
function parseInput(input: string, language: 'flow' | 'typescript'): any {
// Extract the first line to quickly check for custom test directives
if (language === 'flow') {
return HermesParser.parse(input, {
@@ -61,35 +56,49 @@ function parseInput(
return babelParse(input, {
plugins: ['typescript', 'jsx'],
sourceType: 'module',
}) as ParseResult<t.File>;
});
}
}
function invokeCompiler(
function parseFunctions(
source: string,
language: 'flow' | 'typescript',
options: PluginOptions,
): CompilerTransformOutput {
const ast = parseInput(source, language);
let result = transformFromAstSync(ast, source, {
filename: '_playgroundFile.js',
highlightCode: false,
retainLines: true,
plugins: [[BabelPluginReactCompiler, options]],
ast: true,
sourceType: 'module',
configFile: false,
sourceMaps: true,
babelrc: false,
});
if (result?.ast == null || result?.code == null || result?.map == null) {
throw new Error('Expected successful compilation');
): Array<
| NodePath<t.FunctionDeclaration>
| NodePath<t.ArrowFunctionExpression>
| NodePath<t.FunctionExpression>
> {
const items: Array<
| NodePath<t.FunctionDeclaration>
| NodePath<t.ArrowFunctionExpression>
| NodePath<t.FunctionExpression>
> = [];
try {
const ast = parseInput(source, language);
traverse(ast, {
FunctionDeclaration(nodePath) {
items.push(nodePath);
nodePath.skip();
},
ArrowFunctionExpression(nodePath) {
items.push(nodePath);
nodePath.skip();
},
FunctionExpression(nodePath) {
items.push(nodePath);
nodePath.skip();
},
});
} catch (e) {
console.error(e);
CompilerError.throwInvalidJS({
reason: String(e),
description: null,
loc: null,
suggestions: null,
});
}
return {
code: result.code,
sourceMaps: result.map,
language,
};
return items;
}
const COMMON_HOOKS: Array<[string, Hook]> = [
@@ -140,6 +149,37 @@ const COMMON_HOOKS: Array<[string, Hook]> = [
],
];
function isHookName(s: string): boolean {
return /^use[A-Z0-9]/.test(s);
}
function getReactFunctionType(id: t.Identifier | null): ReactFunctionType {
if (id != null) {
if (isHookName(id.name)) {
return 'Hook';
}
const isPascalCaseNameSpace = /^[A-Z].*/;
if (isPascalCaseNameSpace.test(id.name)) {
return 'Component';
}
}
return 'Other';
}
function getFunctionIdentifier(
fn:
| NodePath<t.FunctionDeclaration>
| NodePath<t.ArrowFunctionExpression>
| NodePath<t.FunctionExpression>,
): t.Identifier | null {
if (fn.isArrowFunctionExpression()) {
return null;
}
const id = fn.get('id');
return Array.isArray(id) === false && id.isIdentifier() ? id.node : null;
}
function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
const results = new Map<string, Array<PrintedCompilerPipelineValue>>();
const error = new CompilerError();
@@ -157,63 +197,81 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
} else {
language = 'typescript';
}
let transformOutput;
let count = 0;
const withIdentifier = (id: t.Identifier | null): t.Identifier => {
if (id != null && id.name != null) {
return id;
} else {
return t.identifier(`anonymous_${count++}`);
}
};
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
const logIR = (result: CompilerPipelineValue): void => {
switch (result.kind) {
case 'ast': {
break;
}
case 'hir': {
upsert({
kind: 'hir',
fnName: result.value.id,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
break;
}
case 'reactive': {
upsert({
kind: 'reactive',
fnName: result.value.id,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
case 'debug': {
upsert({
kind: 'debug',
fnName: null,
name: result.name,
value: result.value,
});
break;
}
default: {
const _: never = result;
throw new Error(`Unhandled result ${result}`);
const config = parseConfigPragma(pragma);
for (const fn of parseFunctions(source, language)) {
const id = withIdentifier(getFunctionIdentifier(fn));
for (const result of runPlayground(
fn,
{
...config,
customHooks: new Map([...COMMON_HOOKS]),
},
getReactFunctionType(id),
)) {
const fnName = id.name;
switch (result.kind) {
case 'ast': {
upsert({
kind: 'ast',
fnName,
name: result.name,
value: {
type: 'FunctionDeclaration',
id: withIdentifier(result.value.id),
async: result.value.async,
generator: result.value.generator,
body: result.value.body,
params: result.value.params,
},
});
break;
}
case 'hir': {
upsert({
kind: 'hir',
fnName,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
break;
}
case 'reactive': {
upsert({
kind: 'reactive',
fnName,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
case 'debug': {
upsert({
kind: 'debug',
fnName,
name: result.name,
value: result.value,
});
break;
}
default: {
const _: never = result;
throw new Error(`Unhandled result ${result}`);
}
}
}
};
const parsedOptions = parseConfigPragmaForTests(pragma, {
compilationMode: 'infer',
});
const opts: PluginOptions = parsePluginOptions({
...parsedOptions,
environment: {
...parsedOptions.environment,
customHooks: new Map([...COMMON_HOOKS]),
},
logger: {
debugLogIRs: logIR,
logEvent: () => {},
},
});
transformOutput = invokeCompiler(source, language, opts);
}
} catch (err) {
/**
* error might be an invariant violation or other runtime error
@@ -240,7 +298,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
if (error.hasErrors()) {
return [{kind: 'err', results, error: error}, language];
}
return [{kind: 'ok', results, transformOutput}, language];
return [{kind: 'ok', results}, language];
}
export default function Editor(): JSX.Element {
@@ -260,7 +318,7 @@ export default function Editor(): JSX.Element {
} catch (e) {
invariant(e instanceof Error, 'Only Error may be caught.');
enqueueSnackbar(e.message, {
variant: 'warning',
variant: 'message',
...createMessage(
'Bad URL - fell back to the default Playground.',
MessageLevel.Info,

View File

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

View File

@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import generate from '@babel/generator';
import * as t from '@babel/types';
import {
CodeIcon,
DocumentAddIcon,
@@ -19,12 +21,17 @@ import {memo, ReactNode, useEffect, useState} from 'react';
import {type Store} from '../../lib/stores';
import TabbedWindow from '../TabbedWindow';
import {monacoOptions} from './monacoOptions';
import {BabelFileResult} from '@babel/core';
const MemoizedOutput = memo(Output);
export default MemoizedOutput;
export type PrintedCompilerPipelineValue =
| {
kind: 'ast';
name: string;
fnName: string | null;
value: t.FunctionDeclaration;
}
| {
kind: 'hir';
name: string;
@@ -34,17 +41,8 @@ export type PrintedCompilerPipelineValue =
| {kind: 'reactive'; name: string; fnName: string | null; value: string}
| {kind: 'debug'; name: string; fnName: string | null; value: string};
export type CompilerTransformOutput = {
code: string;
sourceMaps: BabelFileResult['map'];
language: 'flow' | 'typescript';
};
export type CompilerOutput =
| {
kind: 'ok';
transformOutput: CompilerTransformOutput;
results: Map<string, Array<PrintedCompilerPipelineValue>>;
}
| {kind: 'ok'; results: Map<string, Array<PrintedCompilerPipelineValue>>}
| {
kind: 'err';
results: Map<string, Array<PrintedCompilerPipelineValue>>;
@@ -63,6 +61,7 @@ async function tabify(
const tabs = new Map<string, React.ReactNode>();
const reorderedTabs = new Map<string, React.ReactNode>();
const concattedResults = new Map<string, string>();
let topLevelFnDecls: Array<t.FunctionDeclaration> = [];
// Concat all top level function declaration results into a single tab for each pass
for (const [passName, results] of compilerOutput.results) {
for (const result of results) {
@@ -88,6 +87,9 @@ async function tabify(
}
break;
}
case 'ast':
topLevelFnDecls.push(result.value);
break;
case 'debug': {
concattedResults.set(passName, result.value);
break;
@@ -112,17 +114,13 @@ async function tabify(
lastPassOutput = text;
}
// Ensure that JS and the JS source map come first
if (compilerOutput.kind === 'ok') {
const {transformOutput} = compilerOutput;
const sourceMapUrl = getSourceMapUrl(
transformOutput.code,
JSON.stringify(transformOutput.sourceMaps),
);
const code = await prettier.format(transformOutput.code, {
semi: true,
parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts',
plugins: [parserBabel, prettierPluginEstree],
});
if (topLevelFnDecls.length > 0) {
/**
* 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);
reorderedTabs.set(
'JS',
<TextTabContent
@@ -149,6 +147,27 @@ async function tabify(
return reorderedTabs;
}
async function codegen(
ast: t.Program,
source: string,
): Promise<{code: any; sourceMapUrl: string | null}> {
const generated = generate(
ast,
{sourceMaps: true, sourceFileName: 'input.js'},
source,
);
const sourceMapUrl = getSourceMapUrl(
generated.code,
JSON.stringify(generated.map),
);
const codegenOutput = await prettier.format(generated.code, {
semi: true,
parser: 'babel',
plugins: [parserBabel, prettierPluginEstree],
});
return {code: codegenOutput, sourceMapUrl};
}
function utf16ToUTF8(s: string): string {
return unescape(encodeURIComponent(s));
}

View File

@@ -6,10 +6,11 @@
*/
import type {Dispatch, ReactNode} from 'react';
import {useEffect, useReducer} from 'react';
import {useReducer} from 'react';
import createContext from '../lib/createContext';
import {emptyStore} from '../lib/defaultStore';
import {saveStore, type Store} from '../lib/stores';
import type {Store} from '../lib/stores';
import {saveStore} from '../lib/stores';
const StoreContext = createContext<Store>();
@@ -30,11 +31,6 @@ export const useStoreDispatch = StoreDispatchContext.useContext;
*/
export function StoreProvider({children}: {children: ReactNode}): JSX.Element {
const [store, dispatch] = useReducer(storeReducer, emptyStore);
useEffect(() => {
if (store !== emptyStore) {
saveStore(store);
}
}, [store]);
return (
<StoreContext.Provider value={store}>
@@ -63,14 +59,19 @@ function storeReducer(store: Store, action: ReducerAction): Store {
switch (action.type) {
case 'setStore': {
const newStore = action.payload.store;
saveStore(newStore);
return newStore;
}
case 'updateFile': {
const {source} = action.payload;
const newStore = {
...store,
source,
};
saveStore(newStore);
return newStore;
}
}

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View File

@@ -3,16 +3,15 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run watch\" \"yarn workspace react-compiler-runtime run watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
"dev": "cd ../.. && concurrently --kill-others -n compiler,runtime,playground \"yarn workspace babel-plugin-react-compiler run build --watch\" \"yarn workspace react-compiler-runtime run build --watch\" \"wait-on packages/babel-plugin-react-compiler/dist/index.js && cd apps/playground && NODE_ENV=development next dev\"",
"build:compiler": "cd ../.. && concurrently -n compiler,runtime \"yarn workspace babel-plugin-react-compiler run build\" \"yarn workspace react-compiler-runtime run build\"",
"build": "yarn build:compiler && next build",
"postbuild": "node ./scripts/downloadFonts.js",
"preinstall": "cd ../.. && yarn install --frozen-lockfile",
"postinstall": "./scripts/link-compiler.sh",
"vercel-build": "yarn build",
"start": "next start",
"lint": "next lint",
"test": "playwright test --workers=4"
"test": "playwright test"
},
"dependencies": {
"@babel/core": "^7.18.9",
@@ -22,9 +21,9 @@
"@babel/plugin-transform-block-scoping": "^7.18.9",
"@babel/plugin-transform-modules-commonjs": "^7.18.9",
"@babel/preset-react": "^7.18.9",
"@babel/preset-typescript": "^7.26.0",
"@babel/preset-typescript": "^7.18.9",
"@babel/traverse": "^7.18.9",
"@babel/types": "7.26.3",
"@babel/types": "7.18.9",
"@heroicons/react": "^1.0.6",
"@monaco-editor/react": "^4.4.6",
"@playwright/test": "^1.42.1",
@@ -34,18 +33,18 @@
"invariant": "^2.2.4",
"lz-string": "^1.5.0",
"monaco-editor": "^0.52.0",
"next": "^15.2.0-canary.64",
"next": "^15.0.1",
"notistack": "^3.0.0-alpha.7",
"prettier": "^3.3.3",
"pretty-format": "^29.3.1",
"re-resizable": "^6.9.16",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react": "19.0.0-rc-77b637d6-20241016",
"react-dom": "19.0.0-rc-77b637d6-20241016"
},
"devDependencies": {
"@types/node": "18.11.9",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"concurrently": "^7.4.0",
@@ -55,5 +54,9 @@
"postcss": "^8.4.31",
"tailwindcss": "^3.2.4",
"wait-on": "^7.2.0"
},
"resolutions": {
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
}
}

View File

@@ -18,12 +18,10 @@ const baseURL = `http://localhost:${PORT}`;
export default defineConfig({
// Timeout per test
timeout: 30 * 1000,
// Run all tests in parallel.
fullyParallel: true,
// Test directory
testDir: path.join(__dirname, '__tests__/e2e'),
// If a test fails, retry it additional 2 times
retries: 3,
retries: 2,
// Artifacts folder where screenshots, videos, and traces are stored.
outputDir: 'test-results/',
// Note: we only use text snapshots, so its safe to omit the host environment name
@@ -38,10 +36,6 @@ export default defineConfig({
reuseExistingServer: !process.env.CI,
},
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
// default 'list' when running locally
reporter: process.env.CI ? 'github' : 'list',
use: {
// Use baseURL so to make navigations relative.
// More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url

View File

@@ -23,15 +23,6 @@
"@babel/highlight" "^7.24.7"
picocolors "^1.0.0"
"@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
dependencies:
"@babel/helper-validator-identifier" "^7.25.9"
js-tokens "^4.0.0"
picocolors "^1.0.0"
"@babel/compat-data@^7.25.2":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb"
@@ -68,17 +59,6 @@
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1"
"@babel/generator@^7.26.10":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.10.tgz#a60d9de49caca16744e6340c3658dfef6138c3f7"
integrity sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==
dependencies:
"@babel/parser" "^7.26.10"
"@babel/types" "^7.26.10"
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab"
@@ -86,13 +66,6 @@
dependencies:
"@babel/types" "^7.24.7"
"@babel/helper-annotate-as-pure@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4"
integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==
dependencies:
"@babel/types" "^7.25.9"
"@babel/helper-compilation-targets@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c"
@@ -104,26 +77,26 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.25.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==
"@babel/helper-create-class-features-plugin@^7.25.0":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14"
integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==
dependencies:
"@babel/helper-annotate-as-pure" "^7.25.9"
"@babel/helper-member-expression-to-functions" "^7.25.9"
"@babel/helper-optimise-call-expression" "^7.25.9"
"@babel/helper-replace-supers" "^7.26.5"
"@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
"@babel/traverse" "^7.26.9"
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-member-expression-to-functions" "^7.24.8"
"@babel/helper-optimise-call-expression" "^7.24.7"
"@babel/helper-replace-supers" "^7.25.0"
"@babel/helper-skip-transparent-expression-wrappers" "^7.24.7"
"@babel/traverse" "^7.25.4"
semver "^6.3.1"
"@babel/helper-member-expression-to-functions@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3"
integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==
"@babel/helper-member-expression-to-functions@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6"
integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==
dependencies:
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/traverse" "^7.24.8"
"@babel/types" "^7.24.8"
"@babel/helper-module-imports@^7.24.7":
version "7.24.7"
@@ -133,14 +106,6 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-module-imports@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
dependencies:
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.2":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6"
@@ -151,40 +116,26 @@
"@babel/helper-validator-identifier" "^7.24.7"
"@babel/traverse" "^7.25.2"
"@babel/helper-module-transforms@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae"
integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
"@babel/helper-optimise-call-expression@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f"
integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==
dependencies:
"@babel/helper-module-imports" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/traverse" "^7.25.9"
"@babel/helper-optimise-call-expression@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e"
integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==
dependencies:
"@babel/types" "^7.25.9"
"@babel/types" "^7.24.7"
"@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
"@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
"@babel/helper-replace-supers@^7.26.5":
version "7.26.5"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d"
integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
"@babel/helper-replace-supers@^7.25.0":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9"
integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.25.9"
"@babel/helper-optimise-call-expression" "^7.25.9"
"@babel/traverse" "^7.26.5"
"@babel/helper-member-expression-to-functions" "^7.24.8"
"@babel/helper-optimise-call-expression" "^7.24.7"
"@babel/traverse" "^7.25.0"
"@babel/helper-simple-access@^7.24.7":
version "7.24.7"
@@ -194,39 +145,29 @@
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-skip-transparent-expression-wrappers@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9"
integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==
"@babel/helper-skip-transparent-expression-wrappers@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9"
integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==
dependencies:
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7"
"@babel/helper-string-parser@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
"@babel/helper-string-parser@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d"
integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
"@babel/helper-validator-identifier@^7.24.7":
"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
"@babel/helper-validator-identifier@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
"@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d"
integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==
"@babel/helper-validator-option@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
"@babel/helpers@^7.25.0":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60"
@@ -252,13 +193,6 @@
dependencies:
"@babel/types" "^7.25.6"
"@babel/parser@^7.26.10", "@babel/parser@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
dependencies:
"@babel/types" "^7.26.10"
"@babel/plugin-syntax-jsx@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d"
@@ -266,27 +200,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-syntax-jsx@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290"
integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-syntax-typescript@^7.18.9":
"@babel/plugin-syntax-typescript@^7.18.9", "@babel/plugin-syntax-typescript@^7.24.7":
version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff"
integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==
dependencies:
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-syntax-typescript@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399"
integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-block-scoping@^7.18.9":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz#23a6ed92e6b006d26b1869b1c91d1b917c2ea2ac"
@@ -294,7 +214,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-transform-modules-commonjs@^7.18.9":
"@babel/plugin-transform-modules-commonjs@^7.18.9", "@babel/plugin-transform-modules-commonjs@^7.24.7":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c"
integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==
@@ -303,14 +223,6 @@
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-simple-access" "^7.24.7"
"@babel/plugin-transform-modules-commonjs@^7.25.9":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb"
integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==
dependencies:
"@babel/helper-module-transforms" "^7.26.0"
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/plugin-transform-react-display-name@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz#9caff79836803bc666bcfe210aeb6626230c293b"
@@ -344,16 +256,16 @@
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-transform-typescript@^7.25.9":
version "7.26.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz#2e9caa870aa102f50d7125240d9dbf91334b0950"
integrity sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==
"@babel/plugin-transform-typescript@^7.24.7":
version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz#237c5d10de6d493be31637c6b9fa30b6c5461add"
integrity sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==
dependencies:
"@babel/helper-annotate-as-pure" "^7.25.9"
"@babel/helper-create-class-features-plugin" "^7.25.9"
"@babel/helper-plugin-utils" "^7.26.5"
"@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
"@babel/plugin-syntax-typescript" "^7.25.9"
"@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-create-class-features-plugin" "^7.25.0"
"@babel/helper-plugin-utils" "^7.24.8"
"@babel/helper-skip-transparent-expression-wrappers" "^7.24.7"
"@babel/plugin-syntax-typescript" "^7.24.7"
"@babel/preset-react@^7.18.9":
version "7.24.7"
@@ -367,16 +279,16 @@
"@babel/plugin-transform-react-jsx-development" "^7.24.7"
"@babel/plugin-transform-react-pure-annotations" "^7.24.7"
"@babel/preset-typescript@^7.26.0":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d"
integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==
"@babel/preset-typescript@^7.18.9":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz#66cd86ea8f8c014855671d5ea9a737139cbbfef1"
integrity sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==
dependencies:
"@babel/helper-plugin-utils" "^7.25.9"
"@babel/helper-validator-option" "^7.25.9"
"@babel/plugin-syntax-jsx" "^7.25.9"
"@babel/plugin-transform-modules-commonjs" "^7.25.9"
"@babel/plugin-transform-typescript" "^7.25.9"
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/helper-validator-option" "^7.24.7"
"@babel/plugin-syntax-jsx" "^7.24.7"
"@babel/plugin-transform-modules-commonjs" "^7.24.7"
"@babel/plugin-transform-typescript" "^7.24.7"
"@babel/runtime@^7.21.0":
version "7.25.6"
@@ -394,16 +306,7 @@
"@babel/parser" "^7.25.0"
"@babel/types" "^7.25.0"
"@babel/template@^7.26.9":
version "7.26.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/parser" "^7.26.9"
"@babel/types" "^7.26.9"
"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2":
"@babel/traverse@^7.18.9", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.4":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41"
integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==
@@ -416,34 +319,22 @@
debug "^4.3.1"
globals "^11.1.0"
"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380"
integrity sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==
"@babel/types@7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f"
integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg==
dependencies:
"@babel/code-frame" "^7.26.2"
"@babel/generator" "^7.26.10"
"@babel/parser" "^7.26.10"
"@babel/template" "^7.26.9"
"@babel/types" "^7.26.10"
debug "^4.3.1"
globals "^11.1.0"
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
"@babel/types@7.26.3", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==
"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6"
integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.26.9":
version "7.26.10"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259"
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
dependencies:
"@babel/helper-string-parser" "^7.25.9"
"@babel/helper-validator-identifier" "^7.25.9"
"@babel/helper-string-parser" "^7.24.8"
"@babel/helper-validator-identifier" "^7.24.7"
to-fast-properties "^2.0.0"
"@emnapi/runtime@^1.2.0":
version "1.3.1"
@@ -698,10 +589,10 @@
dependencies:
"@monaco-editor/loader" "^1.4.0"
"@next/env@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.2.0-canary.66.tgz#c4ca0d502ad099c68927643df9c9b5d75c7b7fbb"
integrity sha512-/RxW1GJ7a6MJOQ7LOa2bcli7VTjqB7jPyzXwNJQflcYJH4gz1kP6uzg8+IptLJGFSRB58RBKHJk+q1cD8jongA==
"@next/env@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.0.1.tgz#660fe9303e255cec112d3f4198d2897a24bc60b3"
integrity sha512-lc4HeDUKO9gxxlM5G2knTRifqhsY6yYpwuHspBZdboZe0Gp+rZHBNNSIjmQKDJIdRXiXGyVnSD6gafrbQPvILQ==
"@next/eslint-plugin-next@15.0.1":
version "15.0.1"
@@ -710,45 +601,45 @@
dependencies:
fast-glob "3.3.1"
"@next/swc-darwin-arm64@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.0-canary.66.tgz#368438cf713c439b5b4c44d54b5c3b31ee5b772d"
integrity sha512-sVzNJWTekcLOdqkDMistBGr84AVh9eSu4o5JQNEOdxHry4jiF8lqixpOg0+Twj2RRuv4bx32h5xaRVvCSUpITQ==
"@next/swc-darwin-arm64@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.1.tgz#b80a25f1569bd0ca03eca9473f7e93e64937e404"
integrity sha512-C9k/Xv4sxkQRTA37Z6MzNq3Yb1BJMmSqjmwowoWEpbXTkAdfOwnoKOpAb71ItSzoA26yUTIo6ZhN8rKGu4ExQw==
"@next/swc-darwin-x64@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.0-canary.66.tgz#3ddc3f4f6e86e204727770e5984cabf52f852472"
integrity sha512-Avv6Nf/0j0WVqY72Q0mK2glGhvN7LT7iVF31iBYUe/Cbf2cXBjgpXUVmksJjg+2Fi6uTEpaMoZWSVEpJyPkjVQ==
"@next/swc-darwin-x64@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.1.tgz#00dcf79ec7c638a85c3b9ff2e2de2bfb09c1c250"
integrity sha512-uHl13HXOuq1G7ovWFxCACDJHTSDVbn/sbLv8V1p+7KIvTrYQ5HNoSmKBdYeEKRRCbEmd+OohOgg9YOp8Ux3MBg==
"@next/swc-linux-arm64-gnu@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.0-canary.66.tgz#cd3683bf569c66444340b1e4d876913584e93aea"
integrity sha512-kUPejaStjKpF79fz4525DKQKADtUuE+T6j7IvLQsZuWrSX3a5Mix+i52fdTzMJ+sFGg3v147wopZt6L6JMIxxA==
"@next/swc-linux-arm64-gnu@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.1.tgz#faab5f7ffcc6d1a15e8dea1cb9953966658b39bf"
integrity sha512-LvyhvxHOihFTEIbb35KxOc3q8w8G4xAAAH/AQnsYDEnOvwawjL2eawsB59AX02ki6LJdgDaHoTEnC54Gw+82xw==
"@next/swc-linux-arm64-musl@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.0-canary.66.tgz#453836b11efdb50b91cf8a6cfbce8779f6778dd9"
integrity sha512-U8l8jaZ+BAU5wn3bw7PRqq4vGTpObBt+7JbJLpbDqB1GfkZdCDc4nGtqAfLy3pY0O4lEfqal9jrsEVtUBCbfHg==
"@next/swc-linux-arm64-musl@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.1.tgz#97abada9a782ab5b3cb42cf0d4799cbc2e733351"
integrity sha512-vFmCGUFNyk/A5/BYcQNhAQqPIw01RJaK6dRO+ZEhz0DncoW+hJW1kZ8aH2UvTX27zPq3m85zN5waMSbZEmANcQ==
"@next/swc-linux-x64-gnu@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.0-canary.66.tgz#f02b295febaacf8d041f9f149c30c41aea16a81f"
integrity sha512-c+AV8ZN1znGBHu5BACGym+9FhV8+213XVHFI7i2J7TSsJ6T+Eofhwn0tSn1Vy/XpVmpyoEdkwepL+djbdQAGhw==
"@next/swc-linux-x64-gnu@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.1.tgz#548bd47c49fe6d819302139aff8766eb704322e2"
integrity sha512-5by7IYq0NCF8rouz6Qg9T97jYU68kaClHPfGpQG2lCZpSYHtSPQF1kjnqBTd34RIqPKMbCa4DqCufirgr8HM5w==
"@next/swc-linux-x64-musl@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.0-canary.66.tgz#f942c000ba3ffc0289520d25fa1067a75e72fa41"
integrity sha512-4mTIv86qyXuo8NfjigSQ7rk2cDwM8/f8R/kf3hNh8NF1Aaat2RrEet9a/SbsuNpdhhNnPI5RcRwpIJx2JQSPcQ==
"@next/swc-linux-x64-musl@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.1.tgz#84423fbd3a058dd6ae8322e530878f0ec7a1027a"
integrity sha512-lmYr6H3JyDNBJLzklGXLfbehU3ay78a+b6UmBGlHls4xhDXBNZfgb0aI67sflrX+cGBnv1LgmWzFlYrAYxS1Qw==
"@next/swc-win32-arm64-msvc@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.0-canary.66.tgz#0ffdcf5c74b5aa6214307f2ae4aa84f1526e6bf9"
integrity sha512-NPnfsDQXk44h8VtncWq2AgLjHDbUMsc8Tpz7pcLe9qb8lZSxZ9jYbV7NwKzgd+qJbjy/58vgCWhL5PhyXDlWwQ==
"@next/swc-win32-arm64-msvc@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.1.tgz#723c2ced12a998fb40dc901b8faea9170e788c2f"
integrity sha512-DS8wQtl6diAj0eZTdH0sefykm4iXMbHT4MOvLwqZiIkeezKpkgPFcEdFlz3vKvXa2R/2UEgMh48z1nEpNhjeOQ==
"@next/swc-win32-x64-msvc@15.2.0-canary.66":
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.0-canary.66.tgz#458245850cf407d2551155e4662785c109f58bda"
integrity sha512-L/ef++GJqW+T3g2x6mrZ2vrBK+6QS9Ieam8EqK9dG7cFKv7Gqm9yrHvDuVse62hnNB11ZdxfDDKrs9vabuQLlw==
"@next/swc-win32-x64-msvc@15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.1.tgz#ec7e3befc0bcc47527537b1eda2b3745beb15a09"
integrity sha512-4Ho2ggvDdMKlZ/0e9HNdZ9ngeaBwtc+2VS5oCeqrbXqOgutX6I4U2X/42VBw0o+M5evn4/7v3zKgGHo+9v/VjA==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@@ -825,12 +716,12 @@
resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
"@swc/helpers@0.5.15":
version "0.5.15"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==
"@swc/helpers@0.5.13":
version "0.5.13"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c"
integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==
dependencies:
tslib "^2.8.0"
tslib "^2.4.0"
"@types/json5@^0.0.29":
version "0.0.29"
@@ -842,15 +733,17 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
"@types/react-dom@^19.0.0":
version "19.0.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.4.tgz#bedba97f9346bd4c0fe5d39e689713804ec9ac89"
integrity sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==
"@types/react-dom@npm:types-react-dom@19.0.0-rc.1":
version "19.0.0-rc.1"
resolved "https://registry.yarnpkg.com/types-react-dom/-/types-react-dom-19.0.0-rc.1.tgz#1d544d02c5df2a82d87c2eff979afa2e21a8317a"
integrity sha512-VSLZJl8VXCD0fAWp7DUTFUDCcZ8DVXOQmjhJMD03odgeFmu14ZQJHCXeETm3BEAhJqfgJaFkLnGkQv88sRx0fQ==
dependencies:
"@types/react" "*"
"@types/react@^19.0.0":
version "19.0.10"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.10.tgz#d0c66dafd862474190fe95ce11a68de69ed2b0eb"
integrity sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==
"@types/react@*", "@types/react@npm:types-react@19.0.0-rc.1":
version "19.0.0-rc.1"
resolved "https://registry.yarnpkg.com/types-react/-/types-react-19.0.0-rc.1.tgz#576d1a702f6d0cc5b24813a293913e5cbfeaa647"
integrity sha512-RshndUfqTW6K3STLPis8BtAYCGOkMbtvYsi90gmVNDZBXUyUc5juf2PE9LfS/JmOlUIRO8cWTS/1MTnmhjDqyQ==
dependencies:
csstype "^3.0.2"
@@ -2575,11 +2468,6 @@ jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
@@ -2794,27 +2682,27 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
next@^15.2.0-canary.64:
version "15.2.0-canary.66"
resolved "https://registry.yarnpkg.com/next/-/next-15.2.0-canary.66.tgz#cb5ee4453c88f247b6e74fe33fd181eca58e7c86"
integrity sha512-S+gsEu8vxxejI7nKqtCLqZlTi9L40xelLRK/Fgtvm/XT8W8ziLp3KMtN4I9Si5nEMU5uv7bllIfd04kVX8+HIw==
next@^15.0.1:
version "15.0.1"
resolved "https://registry.yarnpkg.com/next/-/next-15.0.1.tgz#a0e8eda35d803cb7f8092b2a2eb9d072e22bf21d"
integrity sha512-PSkFkr/w7UnFWm+EP8y/QpHrJXMqpZzAXpergB/EqLPOh4SGPJXv1wj4mslr2hUZBAS9pX7/9YLIdxTv6fwytw==
dependencies:
"@next/env" "15.2.0-canary.66"
"@next/env" "15.0.1"
"@swc/counter" "0.1.3"
"@swc/helpers" "0.5.15"
"@swc/helpers" "0.5.13"
busboy "1.6.0"
caniuse-lite "^1.0.30001579"
postcss "8.4.31"
styled-jsx "5.1.6"
optionalDependencies:
"@next/swc-darwin-arm64" "15.2.0-canary.66"
"@next/swc-darwin-x64" "15.2.0-canary.66"
"@next/swc-linux-arm64-gnu" "15.2.0-canary.66"
"@next/swc-linux-arm64-musl" "15.2.0-canary.66"
"@next/swc-linux-x64-gnu" "15.2.0-canary.66"
"@next/swc-linux-x64-musl" "15.2.0-canary.66"
"@next/swc-win32-arm64-msvc" "15.2.0-canary.66"
"@next/swc-win32-x64-msvc" "15.2.0-canary.66"
"@next/swc-darwin-arm64" "15.0.1"
"@next/swc-darwin-x64" "15.0.1"
"@next/swc-linux-arm64-gnu" "15.0.1"
"@next/swc-linux-arm64-musl" "15.0.1"
"@next/swc-linux-x64-gnu" "15.0.1"
"@next/swc-linux-x64-musl" "15.0.1"
"@next/swc-win32-arm64-msvc" "15.0.1"
"@next/swc-win32-x64-msvc" "15.0.1"
sharp "^0.33.5"
node-releases@^2.0.18:
@@ -3137,12 +3025,12 @@ re-resizable@^6.9.16:
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.10.0.tgz#d684a096ab438f1a93f59ad3a580a206b0ce31ee"
integrity sha512-hysSK0xmA5nz24HBVztlk4yCqCLCvS32E6ZpWxVKop9x3tqCa4yAj1++facrmkOf62JsJHjmjABdKxXofYioCw==
react-dom@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57"
integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==
react-dom@19.0.0-rc-77b637d6-20241016:
version "19.0.0-rc-77b637d6-20241016"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0-rc-77b637d6-20241016.tgz#71afcba4abbd81a73e85086029202423cf85355e"
integrity sha512-xp5LvY+O6uvg0fNbSMyMXe0kbgzw6qn0mbqrdXStm4LBpFeMswLZ+XSNr+eJ0HyIiWrCw0rrXaVdqOxc9wtdKA==
dependencies:
scheduler "^0.25.0"
scheduler "0.25.0-rc-77b637d6-20241016"
react-is@^16.13.1:
version "16.13.1"
@@ -3154,10 +3042,10 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd"
integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==
react@19.0.0-rc-77b637d6-20241016:
version "19.0.0-rc-77b637d6-20241016"
resolved "https://registry.yarnpkg.com/react/-/react-19.0.0-rc-77b637d6-20241016.tgz#9e20f116d0195979f192537e00a0fa1687680319"
integrity sha512-9A+i+PGSH/P4MezU4w38K9cbJuy0pzsXoPjPWIv6TQGCFmc5qCzC+8yce8dzfSEF1KJgCF2CLc5qtq/ePfiVqg==
read-cache@^1.0.0:
version "1.0.0"
@@ -3279,10 +3167,10 @@ safe-regex-test@^1.0.3:
es-errors "^1.3.0"
is-regex "^1.1.4"
scheduler@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0.tgz#336cd9768e8cceebf52d3c80e3dcf5de23e7e015"
integrity sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==
scheduler@0.25.0-rc-77b637d6-20241016:
version "0.25.0-rc-77b637d6-20241016"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0-rc-77b637d6-20241016.tgz#ab8f8d1cccc9668946caaa1103acdcdb5c871122"
integrity sha512-R5NTrZXJaW4Dj2jHmad2MTehpFq4yUQOxRKDNV7clP1q4Pz6RtUIcofdPnGUWM0krlJAw8DHd/4jT41pFK4iEg==
semver@^6.3.1:
version "6.3.1"
@@ -3629,6 +3517,11 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -3666,11 +3559,6 @@ tslib@^2.1.0, tslib@^2.4.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
tslib@^2.8.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"

View File

@@ -0,0 +1,21 @@
[package]
name = "react_build_hir"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
react_hir = { workspace = true }
react_estree = { workspace = true}
indexmap = { workspace = true }
react_diagnostics = { workspace = true }
react_semantic_analysis = { workspace = true }
thiserror = { workspace = true }

View File

@@ -0,0 +1,3 @@
# Build-HIR
This crate converts from `react_estree` into React Compiler's HIR format as the first phase of compilation.

View File

@@ -0,0 +1,746 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use react_diagnostics::Diagnostic;
use react_estree::{
AssignmentPropertyOrRestElement, AssignmentTarget, BlockStatement, Expression,
ExpressionOrSpread, ExpressionOrSuper, ForInit, Function, IntoFunction, JsValue, Pattern,
Statement, VariableDeclaration, VariableDeclarationKind,
};
use react_hir::{
ArrayDestructureItem, BlockKind, BranchTerminal, Destructure, DestructurePattern, Environment,
ForTerminal, GotoKind, Identifier, IdentifierOperand, InstructionKind, InstructionValue,
JSXAttribute, JSXElement, LValue, LoadGlobal, LoadLocal, ObjectDestructureItem,
ObjectDestructureProperty, PlaceOrSpread, TerminalValue,
};
use crate::builder::{Builder, LoopScope};
use crate::context::get_context_identifiers;
use crate::error::BuildHIRError;
/// Converts a React function in ESTree format into HIR. Returns the HIR
/// if it was constructed sucessfully, otherwise a list of diagnostics
/// if the input could be not be converted to HIR.
///
/// Failures generally include nonsensical input (`delete 1`) or syntax
/// that is not yet supported.
pub fn build(env: &Environment, fun: &Function) -> Result<Box<react_hir::Function>, Diagnostic> {
let mut builder = Builder::new(env);
let mut params = Vec::with_capacity(fun.params.len());
for param in &fun.params {
match param {
Pattern::Identifier(param) => {
let identifier = lower_identifier_for_assignment(
env,
&mut builder,
InstructionKind::Let,
param,
)?;
params.push(identifier);
}
_ => {
return Err(Diagnostic::todo(
"Support non-identifier params",
param.range(),
));
}
}
}
match &fun.body {
Some(react_estree::FunctionBody::BlockStatement(body)) => {
lower_block_statement(env, &mut builder, body)?
}
Some(react_estree::FunctionBody::Expression(body)) => {
lower_expression(env, &mut builder, body)?;
}
None => {
return Err(Diagnostic::invalid_syntax(
BuildHIRError::EmptyFunction,
fun.range,
));
}
}
// In case the function did not explicitly return, terminate the final
// block with an explicit `return undefined`. If the function *did* return,
// this will be unreachable and get pruned later.
let implicit_return_value = builder.push(InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Undefined,
}));
builder.terminate(
TerminalValue::Return(react_hir::ReturnTerminal {
value: implicit_return_value,
}),
react_hir::BlockKind::Block,
);
let body = builder.build()?;
Ok(Box::new(react_hir::Function {
id: fun.id.as_ref().map(|id| id.name.clone()),
body,
params,
// TODO: populate context!
context: Default::default(),
is_async: fun.is_async,
is_generator: fun.is_generator,
}))
}
fn lower_block_statement(
env: &Environment,
builder: &mut Builder,
stmt: &BlockStatement,
) -> Result<(), Diagnostic> {
for stmt in &stmt.body {
lower_statement(env, builder, stmt, None)?;
}
Ok(())
}
/// Convert a statement to HIR. This will often result in multiple instructions and blocks
/// being created as statements often describe control flow.
fn lower_statement(
env: &Environment,
builder: &mut Builder,
stmt: &Statement,
label: Option<String>,
) -> Result<(), Diagnostic> {
match stmt {
Statement::BlockStatement(stmt) => {
lower_block_statement(env, builder, stmt)?;
}
Statement::BreakStatement(stmt) => {
let block = builder.resolve_break(stmt.label.as_ref())?;
builder.terminate(
TerminalValue::Goto(react_hir::GotoTerminal {
block,
kind: GotoKind::Break,
}),
BlockKind::Block,
);
}
Statement::ContinueStatement(stmt) => {
let block = builder.resolve_continue(stmt.label.as_ref())?;
builder.terminate(
TerminalValue::Goto(react_hir::GotoTerminal {
block,
kind: GotoKind::Continue,
}),
BlockKind::Block,
);
}
Statement::ReturnStatement(stmt) => {
let value = match &stmt.argument {
Some(argument) => lower_expression(env, builder, argument)?,
None => builder.push(InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Undefined,
})),
};
builder.terminate(
TerminalValue::Return(react_hir::ReturnTerminal { value }),
BlockKind::Block,
);
}
Statement::ExpressionStatement(stmt) => {
lower_expression(env, builder, &stmt.expression)?;
}
Statement::EmptyStatement(_) => {
// no-op
}
Statement::VariableDeclaration(stmt) => {
lower_variable_declaration(env, builder, stmt)?;
}
Statement::IfStatement(stmt) => {
// block for what follows the if statement, though this may
// not be reachable
let fallthrough_block = builder.reserve(BlockKind::Block);
let consequent_block = builder.enter(BlockKind::Block, |builder| {
lower_statement(env, builder, &stmt.consequent, None)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: fallthrough_block.id,
kind: GotoKind::Break,
}))
})?;
let alternate_block = builder.enter(BlockKind::Block, |builder| {
if let Some(alternate) = &stmt.alternate {
lower_statement(env, builder, alternate, None)?;
}
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: fallthrough_block.id,
kind: GotoKind::Break,
}))
})?;
let test = lower_expression(env, builder, &stmt.test)?;
let terminal = TerminalValue::If(react_hir::IfTerminal {
test,
consequent: consequent_block,
alternate: alternate_block,
fallthrough: Some(fallthrough_block.id),
});
builder.terminate_with_fallthrough(terminal, fallthrough_block);
}
Statement::ForStatement(stmt) => {
// Block for the loop's test condition
let test_block = builder.reserve(BlockKind::Loop);
// Block for code following the loop
let fallthrough_block = builder.reserve(BlockKind::Block);
let init_block = builder.enter(BlockKind::Loop, |builder| {
if let Some(ForInit::VariableDeclaration(decl)) = &stmt.init {
lower_variable_declaration(env, builder, decl)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: test_block.id,
kind: GotoKind::Break,
}))
} else {
Err(Diagnostic::todo(
BuildHIRError::ForStatementIsMissingInitializer,
None,
))
}
})?;
let update_block = stmt
.update
.as_ref()
.map(|update| {
builder.enter(BlockKind::Loop, |builder| {
lower_expression(env, builder, update)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: test_block.id,
kind: GotoKind::Break,
}))
})
})
.transpose()?;
let body_block = builder.enter(BlockKind::Block, |builder| {
let loop_ = LoopScope {
label,
continue_block: update_block.unwrap_or(test_block.id),
break_block: fallthrough_block.id,
};
builder.enter_loop(loop_, |builder| {
lower_statement(env, builder, &stmt.body, None)?;
Ok(TerminalValue::Goto(react_hir::GotoTerminal {
block: update_block.unwrap_or(test_block.id),
kind: GotoKind::Continue,
}))
})
})?;
let terminal = TerminalValue::For(ForTerminal {
body: body_block,
init: init_block,
test: test_block.id,
fallthrough: fallthrough_block.id,
update: update_block,
});
builder.terminate_with_fallthrough(terminal, test_block);
if let Some(test) = &stmt.test {
let test_value = lower_expression(env, builder, test)?;
let terminal = TerminalValue::Branch(BranchTerminal {
test: test_value,
consequent: body_block,
alternate: fallthrough_block.id,
});
builder.terminate_with_fallthrough(terminal, fallthrough_block);
} else {
return Err(Diagnostic::todo(
BuildHIRError::ForStatementIsMissingTest,
stmt.range,
));
}
}
_ => todo!("Lower {stmt:#?}"),
}
Ok(())
}
fn lower_variable_declaration(
env: &Environment,
builder: &mut Builder,
stmt: &VariableDeclaration,
) -> Result<(), Diagnostic> {
let kind = match stmt.kind {
VariableDeclarationKind::Const => InstructionKind::Const,
VariableDeclarationKind::Let => InstructionKind::Let,
VariableDeclarationKind::Var => {
return Err(Diagnostic::unsupported(
BuildHIRError::VariableDeclarationKindIsVar,
stmt.range,
));
}
};
for declaration in &stmt.declarations {
if let Some(init) = &declaration.init {
let value = lower_expression(env, builder, init)?;
lower_assignment_pattern(env, builder, kind, &declaration.id, value)?;
} else {
match &declaration.id {
Pattern::Identifier(id) => {
let identifier = env.resolve_variable_declaration(id.as_ref(), &id.name);
if let Some(identifier) = identifier {
builder.push(InstructionValue::DeclareLocal(react_hir::DeclareLocal {
lvalue: LValue {
identifier: IdentifierOperand {
identifier,
effect: None,
},
kind,
},
}));
} else {
return Err(Diagnostic::invariant(
BuildHIRError::VariableDeclarationBindingIsNonLocal,
id.range,
));
}
}
_ => {
return Err(Diagnostic::invalid_syntax(
"Expected an identifier for variable declaration without an intializer. Destructuring requires an initial value",
declaration.range,
));
}
}
}
}
Ok(())
}
/// Converts an ESTree Expression into an HIR InstructionValue. Note that while only a single
/// InstructionValue is returned, this function is recursive and may cause multiple instructions
/// to be emitted, possibly across multiple basic blocks (in the case of expressions with control
/// flow semenatics such as logical, conditional, and optional expressions).
fn lower_expression(
env: &Environment,
builder: &mut Builder,
expr: &Expression,
) -> Result<IdentifierOperand, Diagnostic> {
let value = match expr {
Expression::Identifier(expr) => {
let identifier = env.resolve_variable_reference(expr.as_ref());
if let Some(identifier) = identifier {
let place = IdentifierOperand {
effect: None,
identifier,
};
InstructionValue::LoadLocal(LoadLocal { place })
} else {
InstructionValue::LoadGlobal(LoadGlobal {
name: expr.name.clone(),
})
}
}
Expression::Literal(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: expr.value.clone(),
}),
Expression::NumericLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Number(expr.value),
}),
Expression::BooleanLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Boolean(expr.value),
}),
Expression::StringLiteral(expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::String(expr.value.clone()),
}),
Expression::NullLiteral(_expr) => InstructionValue::Primitive(react_hir::Primitive {
value: JsValue::Null,
}),
Expression::ArrayExpression(expr) => {
let mut elements = Vec::with_capacity(expr.elements.len());
for expr in &expr.elements {
let element = match expr {
Some(react_estree::ExpressionOrSpread::SpreadElement(expr)) => Some(
PlaceOrSpread::Spread(lower_expression(env, builder, &expr.argument)?),
),
Some(react_estree::ExpressionOrSpread::Expression(expr)) => {
Some(PlaceOrSpread::Place(lower_expression(env, builder, expr)?))
}
None => None,
};
elements.push(element);
}
InstructionValue::Array(react_hir::Array { elements })
}
Expression::AssignmentExpression(expr) => match expr.operator {
react_estree::AssignmentOperator::Equals => {
let right = lower_expression(env, builder, &expr.right)?;
return lower_assignment(
env,
builder,
InstructionKind::Reassign,
&expr.left,
right,
);
}
_ => todo!("lower assignment expr {:#?}", expr),
},
Expression::BinaryExpression(expr) => {
let left = lower_expression(env, builder, &expr.left)?;
let right = lower_expression(env, builder, &expr.right)?;
InstructionValue::Binary(react_hir::Binary {
left,
operator: expr.operator,
right,
})
}
Expression::FunctionExpression(expr) => {
InstructionValue::Function(lower_function(env, builder, expr.as_ref())?)
}
Expression::ArrowFunctionExpression(expr) => {
InstructionValue::Function(lower_function(env, builder, expr.as_ref())?)
}
Expression::CallExpression(expr) => {
let callee_expr = match &expr.callee {
ExpressionOrSuper::Super(callee) => {
return Err(Diagnostic::unsupported(
BuildHIRError::UnsupportedSuperExpression,
callee.range,
));
}
ExpressionOrSuper::Expression(callee) => callee,
};
if matches!(&callee_expr, Expression::MemberExpression(_)) {
return Err(Diagnostic::todo("Support method calls", expr.range));
}
let callee = lower_expression(env, builder, callee_expr)?;
let arguments = lower_arguments(env, builder, &expr.arguments)?;
InstructionValue::Call(react_hir::Call { callee, arguments })
}
Expression::JSXElement(expr) => {
InstructionValue::JSXElement(lower_jsx_element(env, builder, expr)?)
}
_ => todo!("Lower expr {expr:#?}"),
};
Ok(builder.push(value))
}
fn lower_arguments(
env: &Environment,
builder: &mut Builder,
args: &[ExpressionOrSpread],
) -> Result<Vec<PlaceOrSpread>, Diagnostic> {
let mut arguments = Vec::with_capacity(args.len());
for arg in args {
let element = match arg {
react_estree::ExpressionOrSpread::SpreadElement(arg) => {
PlaceOrSpread::Spread(lower_expression(env, builder, &arg.argument)?)
}
react_estree::ExpressionOrSpread::Expression(arg) => {
PlaceOrSpread::Place(lower_expression(env, builder, arg)?)
}
};
arguments.push(element);
}
Ok(arguments)
}
fn lower_function<T: IntoFunction>(
env: &Environment,
_builder: &mut Builder,
function: &T,
) -> Result<react_hir::FunctionExpression, Diagnostic> {
let context_identifiers = get_context_identifiers(env, function);
let mut context = Vec::new();
let mut seen = HashSet::new();
for declaration_id in context_identifiers {
if let Some(identifier) = env.resolve_declaration_id(declaration_id) {
if !seen.insert(identifier.id) {
continue;
}
context.push(IdentifierOperand {
effect: None,
identifier,
});
}
}
let mut fun = build(env, function.function())?;
fun.context = context;
Ok(react_hir::FunctionExpression {
// TODO: collect dependencies!
dependencies: Default::default(),
lowered_function: fun,
})
}
fn lower_jsx_element(
env: &Environment,
builder: &mut Builder,
expr: &react_estree::JSXElement,
) -> Result<JSXElement, Diagnostic> {
let props: Result<Vec<JSXAttribute>, Diagnostic> = expr
.opening_element
.attributes
.iter()
.map(|attr| lower_jsx_attribute(env, builder, attr))
.collect();
let _props = props?;
let children: Result<Vec<IdentifierOperand>, Diagnostic> = expr
.children
.iter()
.map(|child| {
let child = lower_jsx_child(env, builder, child)?;
Ok(child)
})
.collect();
let _children = children?;
todo!("lower jsx element");
// Ok(JSXElement {
// tag: todo!(),
// props,
// children: if children.is_empty() {
// None
// } else {
// Some(children)
// },
// })
}
fn lower_jsx_attribute(
_env: &Environment,
_builder: &mut Builder,
_attr: &react_estree::JSXAttributeOrSpread,
) -> Result<JSXAttribute, Diagnostic> {
todo!("lower jsx attribute")
}
fn lower_jsx_child(
_env: &Environment,
_builder: &mut Builder,
_child: &react_estree::JSXChildItem,
) -> Result<IdentifierOperand, Diagnostic> {
todo!("lower jsx child")
}
fn lower_assignment(
env: &Environment,
builder: &mut Builder,
kind: InstructionKind,
lvalue: &AssignmentTarget,
value: IdentifierOperand,
) -> Result<IdentifierOperand, Diagnostic> {
Ok(match lvalue {
AssignmentTarget::Pattern(lvalue) => {
lower_assignment_pattern(env, builder, kind, lvalue, value)?
}
_ => todo!("lower assignment for {:#?}", lvalue),
})
}
// TODO: change the success type to void, no caller uses it
fn lower_assignment_pattern(
env: &Environment,
builder: &mut Builder,
kind: InstructionKind,
lvalue: &Pattern,
value: IdentifierOperand,
) -> Result<IdentifierOperand, Diagnostic> {
Ok(match lvalue {
Pattern::Identifier(lvalue) => {
let identifier = lower_identifier_for_assignment(env, builder, kind, lvalue)?;
builder.push(InstructionValue::StoreLocal(react_hir::StoreLocal {
lvalue: LValue { identifier, kind },
value,
}))
}
Pattern::ArrayPattern(lvalue) => {
let mut items = Vec::with_capacity(lvalue.elements.len());
let mut followups: Vec<(Identifier, &Pattern)> = Vec::new();
for element in &lvalue.elements {
match element {
None => items.push(ArrayDestructureItem::Hole),
Some(Pattern::Identifier(element)) => {
let identifier =
lower_identifier_for_assignment(env, builder, kind, element)?;
items.push(ArrayDestructureItem::Value(identifier));
}
Some(Pattern::RestElement(element)) => {
if let Pattern::Identifier(element) = &element.argument {
let identifier = lower_identifier_for_assignment(
env,
builder,
kind,
element.as_ref(),
)?;
items.push(ArrayDestructureItem::Spread(identifier));
} else {
let temporary = env.new_temporary();
items.push(ArrayDestructureItem::Spread(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, &element.argument));
}
}
Some(element) => {
let temporary = env.new_temporary();
items.push(ArrayDestructureItem::Value(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, element));
}
}
}
let temporary = builder.push(InstructionValue::Destructure(Destructure {
kind,
pattern: DestructurePattern::Array(items),
value,
}));
for (temporary, pattern) in followups {
lower_assignment_pattern(
env,
builder,
kind,
pattern,
IdentifierOperand {
identifier: temporary,
effect: None,
},
)?;
}
temporary
}
Pattern::ObjectPattern(lvalue) => {
let mut properties = Vec::with_capacity(lvalue.properties.len());
let mut followups: Vec<(Identifier, &Pattern)> = Vec::new();
for property in &lvalue.properties {
match property {
AssignmentPropertyOrRestElement::RestElement(property) => {
if let Pattern::Identifier(element) = &property.argument {
let identifier = lower_identifier_for_assignment(
env,
builder,
kind,
element.as_ref(),
)?;
properties.push(ObjectDestructureItem::Spread(identifier));
} else {
let temporary = env.new_temporary();
properties.push(ObjectDestructureItem::Spread(IdentifierOperand {
identifier: temporary.clone(),
effect: None,
}));
followups.push((temporary, &property.argument));
}
}
AssignmentPropertyOrRestElement::AssignmentProperty(property) => {
if property.is_computed {
return Err(Diagnostic::todo(
"Handle computed properties in ObjectPattern",
property.range,
));
}
let key = if let Expression::Identifier(key) = &property.key {
key.name.as_str()
} else {
return Err(Diagnostic::todo(
"Support non-identifier object keys in non-computed ObjectPattern",
property.range,
));
};
if let Pattern::Identifier(value) = &property.value {
let value = lower_identifier_for_assignment(env, builder, kind, value)?;
properties.push(ObjectDestructureItem::Property(
ObjectDestructureProperty {
name: key.to_string(),
value,
},
));
} else {
let temporary = env.new_temporary();
properties.push(ObjectDestructureItem::Property(
ObjectDestructureProperty {
name: key.to_string(),
value: IdentifierOperand {
identifier: temporary.clone(),
effect: None,
},
},
));
followups.push((temporary, &property.value));
}
}
}
}
let temporary = builder.push(InstructionValue::Destructure(Destructure {
kind,
pattern: DestructurePattern::Object(properties),
value,
}));
for (temporary, pattern) in followups {
lower_assignment_pattern(
env,
builder,
kind,
pattern,
IdentifierOperand {
identifier: temporary,
effect: None,
},
)?;
}
temporary
}
_ => todo!("lower assignment pattern for {:#?}", lvalue),
})
}
fn lower_identifier_for_assignment(
env: &Environment,
_builder: &mut Builder,
kind: InstructionKind,
node: &react_estree::Identifier,
) -> Result<IdentifierOperand, Diagnostic> {
match kind {
InstructionKind::Reassign => {
let identifier = env.resolve_variable_reference(node);
if let Some(identifier) = identifier {
Ok(IdentifierOperand {
identifier,
effect: None,
})
} else {
// Reassigning a global
Err(
Diagnostic::invalid_react(BuildHIRError::ReassignedGlobal, node.range)
.annotate(format!("Cannot reassign `{}`", &node.name), node.range),
)
}
}
_ => {
// Declaration
let identifier = env.resolve_variable_declaration(node, &node.name).unwrap();
Ok(IdentifierOperand {
identifier,
effect: None,
})
}
}
}

View File

@@ -0,0 +1,322 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::cell::RefCell;
use std::rc::Rc;
use react_diagnostics::Diagnostic;
use react_hir::{
initialize_hir, BasicBlock, BlockId, BlockKind, Blocks, Environment, GotoKind, IdentifierData,
IdentifierOperand, InstrIx, Instruction, InstructionIdGenerator, InstructionValue, Terminal,
TerminalValue, Type, HIR,
};
use crate::BuildHIRError;
/// Helper struct used when converting from ESTree to HIR. Includes:
/// - Variable resolution
/// - Label resolution (for labeled statements and break/continue)
/// - Access to the environment
///
/// As well as representing the incomplete form of the HIR. Usage
/// generally involves driving calls to enter/exit blocks, resolve
/// labels and variables, and then calling `build()` when the HIR
/// is complete.
pub(crate) struct Builder<'e> {
#[allow(dead_code)]
environment: &'e Environment,
completed: Blocks,
instructions: Vec<Instruction>,
entry: BlockId,
wip: WipBlock,
id_gen: InstructionIdGenerator,
scopes: Vec<ControlFlowScope>,
}
pub(crate) struct WipBlock {
pub id: BlockId,
pub kind: BlockKind,
pub instructions: Vec<InstrIx>,
}
#[derive(Clone, PartialEq, Eq, Debug)]
enum ControlFlowScope {
Loop(LoopScope),
// Switch(SwitchScope),
#[allow(dead_code)]
Label(LabelScope),
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct LoopScope {
pub label: Option<String>,
pub continue_block: BlockId,
pub break_block: BlockId,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct LabelScope {
pub label: String,
pub block: BlockId,
}
impl ControlFlowScope {
fn label(&self) -> Option<&String> {
match self {
Self::Loop(scope) => scope.label.as_ref(),
Self::Label(scope) => Some(&scope.label),
}
}
fn break_block(&self) -> BlockId {
match self {
Self::Loop(scope) => scope.break_block,
Self::Label(scope) => scope.block,
}
}
}
impl<'e> Builder<'e> {
pub(crate) fn new(environment: &'e Environment) -> Self {
let entry = environment.next_block_id();
let current = WipBlock {
id: entry,
kind: BlockKind::Block,
instructions: Default::default(),
};
Self {
environment,
completed: Default::default(),
instructions: Default::default(),
entry,
wip: current,
id_gen: InstructionIdGenerator::new(),
scopes: Default::default(),
}
}
/// Completes the builder and returns the HIR if it was valid,
/// or a Diagnostic if a validation error occured.
///
/// TODO: refine the type, only invariants should be possible here,
/// not other types of errors
pub(crate) fn build(self) -> Result<HIR, Diagnostic> {
let mut hir = HIR {
entry: self.entry,
blocks: self.completed,
instructions: self.instructions,
};
// Run all the initialization passes
initialize_hir(&mut hir)?;
Ok(hir)
}
/// Adds a new instruction to the end of the work in progress block
pub(crate) fn push(&mut self, value: InstructionValue) -> IdentifierOperand {
let lvalue = IdentifierOperand {
identifier: self.environment.new_temporary(),
effect: None,
};
let instr = Instruction {
id: self.id_gen.next(),
lvalue: lvalue.clone(),
value,
};
let ix = InstrIx::new(self.instructions.len() as u32);
self.instructions.push(instr);
self.wip.instructions.push(ix);
lvalue
}
/// Terminates the work in progress block with the given terminal, and starts a new
/// work in progress block with the given kind
pub(crate) fn terminate(&mut self, terminal: TerminalValue, next_kind: BlockKind) {
let next_wip = WipBlock {
id: self.environment.next_block_id(),
kind: next_kind,
instructions: Default::default(),
};
self.terminate_with_fallthrough(terminal, next_wip)
}
pub(crate) fn terminate_with_fallthrough(
&mut self,
terminal: TerminalValue,
fallthrough: WipBlock,
) {
let prev_wip = std::mem::replace(&mut self.wip, fallthrough);
self.completed.insert(Box::new(BasicBlock {
id: prev_wip.id,
kind: prev_wip.kind,
instructions: prev_wip.instructions,
terminal: Terminal {
id: self.id_gen.next(),
value: terminal,
},
predecessors: Default::default(),
phis: Default::default(),
}));
}
pub(crate) fn reserve(&mut self, kind: BlockKind) -> WipBlock {
WipBlock {
id: self.environment.next_block_id(),
kind,
instructions: Default::default(),
}
}
pub(crate) fn enter<F>(&mut self, kind: BlockKind, f: F) -> Result<BlockId, Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
let wip = self.reserve(kind);
let id = wip.id;
self.enter_reserved(wip, f)?;
Ok(id)
}
fn enter_reserved<F>(&mut self, wip: WipBlock, f: F) -> Result<(), Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
let current = std::mem::replace(&mut self.wip, wip);
let (result, terminal) = match f(self) {
Ok(terminal) => (Ok(()), terminal),
Err(error) => (
Err(error),
// TODO: add a `Terminal::Error` variant
TerminalValue::Goto(react_hir::GotoTerminal {
block: current.id,
kind: GotoKind::Break,
}),
),
};
let completed = std::mem::replace(&mut self.wip, current);
self.completed.insert(Box::new(BasicBlock {
id: completed.id,
kind: completed.kind,
instructions: completed.instructions,
terminal: Terminal {
id: self.id_gen.next(),
value: terminal,
},
predecessors: Default::default(),
phis: Default::default(),
}));
result
}
pub(crate) fn enter_loop<F>(
&mut self,
scope: LoopScope,
f: F,
) -> Result<TerminalValue, Diagnostic>
where
F: FnOnce(&mut Self) -> Result<TerminalValue, Diagnostic>,
{
self.scopes.push(ControlFlowScope::Loop(scope.clone()));
let terminal = f(self);
let last = self.scopes.pop().unwrap();
assert_eq!(last, ControlFlowScope::Loop(scope));
terminal
}
/// Returns a new temporary identifier
/// This may be necessary for destructuring with default values. there
/// we synthesize a temporary identifier to store the possibly-missing value
/// into, and emit a later StoreLocal for the original identifier
#[allow(dead_code)]
pub(crate) fn make_temporary(&self) -> react_hir::Identifier {
react_hir::Identifier {
id: self.environment.next_identifier_id(),
name: None,
data: Rc::new(RefCell::new(IdentifierData {
mutable_range: Default::default(),
scope: None,
type_: Type::Var(self.environment.next_type_var_id()),
})),
}
}
/// Resolves the target for the given break label (if present), or returns the default
/// break target given the current context. Returns a diagnostic if the label is
/// provided but cannot be resolved.
pub(crate) fn resolve_break(
&self,
label: Option<&react_estree::Identifier>,
) -> Result<BlockId, Diagnostic> {
for scope in self.scopes.iter().rev() {
match (label, scope.label()) {
// If this is an unlabeled break, return the most recent break target
(None, _) => return Ok(scope.break_block()),
// If the break is labeled and matches the current scope, return its break target
(Some(label), Some(scope_label)) if &label.name == scope_label => {
return Ok(scope.break_block());
}
// Otherwise keep searching
_ => continue,
}
}
Err(Diagnostic::invalid_syntax(
BuildHIRError::UnresolvedBreakTarget,
None,
))
}
/// Resolves the target for the given continue label (if present), or returns the default
/// continue target given the current context. Returns a diagnostic if the label is
/// provided but cannot be resolved.
pub(crate) fn resolve_continue(
&self,
label: Option<&react_estree::Identifier>,
) -> Result<BlockId, Diagnostic> {
for scope in self.scopes.iter().rev() {
match scope {
ControlFlowScope::Loop(scope) => {
match (label, &scope.label) {
// If this is an unlabeled continue, return the first matching loop
(None, _) => return Ok(scope.continue_block),
// If the continue is labeled and matches the current scope, return its continue target
(Some(label), Some(scope_label))
if label.name.as_str() == scope_label.as_str() =>
{
return Ok(scope.continue_block);
}
// Otherwise keep searching
_ => continue,
}
}
_ => {
match (label, scope.label()) {
(Some(label), Some(scope_label)) if label.name.as_str() == scope_label => {
// Error, the continue referred to a label that is not a loop
return Err(Diagnostic::invalid_syntax(
BuildHIRError::ContinueTargetIsNotALoop,
None,
));
}
_ => continue,
}
}
}
}
Err(Diagnostic::invalid_syntax(
BuildHIRError::UnresolvedContinueTarget,
None,
))
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use react_estree::IntoFunction;
use react_hir::Environment;
use react_semantic_analysis::{DeclarationId, ScopeView};
pub(crate) fn get_context_identifiers<T: IntoFunction>(
env: &Environment,
node: &T,
) -> Vec<DeclarationId> {
let function_scope = env.scope(node.function()).unwrap();
let mut free = FreeVariables::default();
let mut seen = HashSet::new();
populate_free_variable_references(&mut free, &mut seen, function_scope);
free
}
type FreeVariables = Vec<DeclarationId>;
fn populate_free_variable_references(
free: &mut FreeVariables,
seen: &mut HashSet<DeclarationId>,
scope: ScopeView<'_>,
) {
for reference in scope.references() {
if !seen.insert(reference.declaration().id()) {
continue;
}
let declaration_scope = reference.declaration().scope();
if !declaration_scope.is_descendant_of(scope) {
free.push(reference.declaration().id())
}
}
for child in scope.children() {
populate_free_variable_references(free, seen, child);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use thiserror::Error;
/// Errors which can occur during HIR construction
#[derive(Error, Debug)]
pub enum BuildHIRError {
/// ErrorSeverity::Unsupported
#[error(
"Variable declarations must be `let` or `const`, `var` declarations are not supported"
)]
VariableDeclarationKindIsVar,
/// ErrorSeverity::Invariant
#[error("Invariant: Expected variable declaration to declare a fresh binding")]
VariableDeclarationBindingIsNonLocal,
/// ErrorSeverity::Todo
#[error("`for` statements must have an initializer, eg `for (**let i = 0**; ...)`")]
ForStatementIsMissingInitializer,
/// ErrorSeverity::Todo
#[error(
"`for` statements must have a test condition, eg `for (let i = 0; **i < count**; ...)`"
)]
ForStatementIsMissingTest,
/// ErrorSeverity::Invariant
#[error("Invariant: Expected an expression node")]
NonExpressionInExpressionPosition,
/// ErrorSeverity::InvalidReact
#[error("React functions may not reassign variables defined outside of the component or hook")]
ReassignedGlobal,
/// ErrorSeverity::InvalidSyntax
#[error("Could not resolve a target for `break` statement")]
UnresolvedBreakTarget,
/// ErrorSeverity::InvalidSyntax
#[error("Could not resolve a target for `continue` statement")]
UnresolvedContinueTarget,
/// ErrorSeverity::InvalidSyntax
#[error("Labeled `continue` statements must use the label of a loop statement")]
ContinueTargetIsNotALoop,
/// ErrorSeverity::Invariant
#[error("Invariant: Identifier was not resolved (did name resolution run successfully?)")]
UnknownIdentifier,
/// ErrorSeverity::InvalidSyntax
#[error("Expected function to have a body")]
EmptyFunction,
#[error("`super` is not suppported")]
UnsupportedSuperExpression,
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod build;
mod builder;
mod context;
mod error;
pub use build::build;
pub use error::*;

View File

@@ -0,0 +1,23 @@
[package]
name = "react_diagnostics"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# TODO: extract SourceRange into a separate crate so that
# we don't depend on full estree here
react_estree = { workspace = true }
# TODO: consider extracting a separate react_miette crate which does
# the translation from react_diagnostics::Diagnostic to miette::Diagnostic
miette = { workspace = true }
thiserror = { workspace = true }
static_assertions = { workspace = true }

View File

@@ -0,0 +1,10 @@
# react_diagnostics
Types for representing compiler diagnostics. Includes a general-purpose representation
of diagnostics with related information which can be converted into `miette::Diagnostic` to exploit miette's pretty printing of errors.
Unlike miette, lsp_types, and other diagnostic libraries, the error severities match
React Compiler's semantics. The intent is that a given diagnostic may be displayed as
an error, warning, or not displayed at all depending on the context in which the
compiler is being used. For example, an ESLint plugin powered by React Compiler may ignore
InvalidSyntax diagnostics, whereas the regular compiler may report them as errors.

View File

@@ -0,0 +1,285 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::error::Error;
use std::fmt::{Debug, Display, Write};
use miette::SourceSpan;
use react_estree::SourceRange;
use static_assertions::assert_impl_all;
use thiserror::Error;
pub type Diagnostics = Vec<Diagnostic>;
pub type DiagnosticsResult<T> = Result<T, Diagnostics>;
#[derive(Debug)]
pub struct WithDiagnostics<T> {
pub item: T,
pub diagnostics: Vec<Diagnostic>,
}
impl<T> From<WithDiagnostics<T>> for Result<T, Diagnostics> {
fn from(s: WithDiagnostics<T>) -> Result<T, Diagnostics> {
if s.diagnostics.is_empty() {
Ok(s.item)
} else {
Err(s.diagnostics)
}
}
}
pub fn diagnostics_result<T>(result: T, diagnostics: Diagnostics) -> DiagnosticsResult<T> {
if diagnostics.is_empty() {
Ok(result)
} else {
Err(diagnostics)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)]
pub enum DiagnosticSeverity {
/// A feature that is intended to work but not yet implemented
#[error("Not implemented")]
Todo,
/// Syntax that is valid but intentionally not supported
#[error("Unsupported")]
Unsupported,
/// Invalid syntax
#[error("Invalid JavaScript")]
InvalidSyntax,
/// Valid syntax, but invalid React
#[error("Invalid React")]
InvalidReact,
/// Internal compiler error (ICE)
#[error("Internal error")]
Invariant,
}
/// A diagnostic message as a result of validating some code. This struct is
/// modeled after the LSP Diagnostic type:
/// https://microsoft.github.io/language-server-protocol/specification#diagnostic
///
/// Changes from LSP:
/// - `location` is different from LSP in that it's a file + span instead of
/// just a span.
/// - Unused fields are omitted.
/// - Severity is a custom enum that represents React-specific categories of error.
/// The translation to an LSP error/warning/etc depends on compiler settings and
/// invocation context.
#[derive(Debug)]
pub struct Diagnostic(Box<DiagnosticData>);
impl Diagnostic {
fn with_severity<T: 'static + DiagnosticDisplay>(
severity: DiagnosticSeverity,
message: T,
range: Option<SourceRange>,
) -> Self {
Self(Box::new(DiagnosticData {
message: Box::new(message),
span: range.map(source_span_from_range),
related_information: Vec::new(),
severity,
data: Vec::new(),
}))
}
/// Creates a new Todo Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn todo<T: 'static + DiagnosticDisplay>(message: T, range: Option<SourceRange>) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Todo, message, range)
}
/// Creates a new Unsupported Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn unsupported<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Unsupported, message, range)
}
/// Creates a new InvalidSyntax Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invalid_syntax<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::InvalidSyntax, message, range)
}
/// Creates a new InvalidReact Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invalid_react<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::InvalidReact, message, range)
}
/// Creates a new InvalidReact Diagnostic.
/// Additional locations can be added with the `.annotate()` function.
pub fn invariant<T: 'static + DiagnosticDisplay>(
message: T,
range: Option<SourceRange>,
) -> Self {
Diagnostic::with_severity(DiagnosticSeverity::Invariant, message, range)
}
/// Annotates this error with an additional location and associated message.
pub fn annotate<T: 'static + DiagnosticDisplay>(
mut self,
message: T,
range: Option<SourceRange>,
) -> Self {
self.0
.related_information
.push(DiagnosticRelatedInformation {
message: Box::new(message),
span: range.map(source_span_from_range),
});
self
}
pub fn message(&self) -> &impl DiagnosticDisplay {
&self.0.message
}
pub fn span(&self) -> Option<SourceSpan> {
self.0.span
}
pub fn get_data(&self) -> &[impl DiagnosticDisplay] {
&self.0.data
}
pub fn severity(&self) -> DiagnosticSeverity {
self.0.severity
}
pub fn related_information(&self) -> &[DiagnosticRelatedInformation] {
&self.0.related_information
}
pub fn print_without_source(&self) -> String {
let mut result = String::new();
writeln!(
result,
"{message}:{span:?}",
message = &self.0.message,
span = self.0.span
)
.unwrap();
if !self.0.related_information.is_empty() {
for (ix, related) in self.0.related_information.iter().enumerate() {
writeln!(
result,
"[related {ix}] {message}:{span:?}",
ix = ix + 1,
message = related.message,
span = related.span
)
.unwrap();
}
};
result
}
}
impl Display for Diagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.message)
}
}
impl Error for Diagnostic {}
impl miette::Diagnostic for Diagnostic {
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
Some(Box::new(self.0.message.to_string()))
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let related_items = &self.0.related_information;
let mut spans: Vec<miette::LabeledSpan> = Vec::new();
for related in related_items {
if let Some(span) = related.span {
spans.push(miette::LabeledSpan::new_with_span(
Some(related.message.to_string()),
span,
))
}
}
if spans.is_empty() {
if let Some(span) = self.0.span {
spans.push(miette::LabeledSpan::new_with_span(
Some(self.0.message.to_string()),
span,
))
}
}
Some(Box::new(spans.into_iter()))
}
}
// Ensure Diagnostic is thread-safe
assert_impl_all!(Diagnostic: Send, Sync);
#[derive(Debug)]
struct DiagnosticData {
/// Human readable error message.
message: Box<dyn DiagnosticDisplay>,
/// The primary location of this diagnostic.
span: Option<SourceSpan>,
/// Related diagnostic information, such as other definitions in the case of
/// a duplicate definition error.
related_information: Vec<DiagnosticRelatedInformation>,
severity: DiagnosticSeverity,
/// A list with data that can be passed to the code actions
/// `data` is used in the LSP protocol:
/// @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
data: Vec<Box<dyn DiagnosticDisplay>>,
}
/// Secondary locations attached to a diagnostic.
#[derive(Debug)]
pub struct DiagnosticRelatedInformation {
/// The message of this related diagnostic information.
pub message: Box<dyn DiagnosticDisplay>,
/// The location of this related diagnostic information.
pub span: Option<SourceSpan>,
}
/// Trait for diagnostic messages to allow structs that capture
/// some data and can lazily convert it to a message.
pub trait DiagnosticDisplay: Debug + Display + Send + Sync {}
/// Automatically implement the trait if constraints are met, so that
/// implementors don't need to.
impl<T> DiagnosticDisplay for T where T: Debug + Display + Send + Sync {}
impl From<Diagnostic> for Diagnostics {
fn from(diagnostic: Diagnostic) -> Self {
vec![diagnostic]
}
}
fn source_span_from_range(range: SourceRange) -> SourceSpan {
SourceSpan::new(
(range.start as usize).into(),
((u32::from(range.end) - range.start) as usize).into(),
)
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod diagnostic;
pub use diagnostic::*;
/// Returns Ok(()) if the condition is true, otherwise returns Err()
/// with the diagnostic produced by the provided callback
pub fn invariant<F>(cond: bool, f: F) -> Result<(), Diagnostic>
where
F: Fn() -> Diagnostic,
{
if cond { Ok(()) } else { Err(f()) }
}

View File

@@ -0,0 +1,22 @@
[package]
name = "react_estree"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
insta = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
static_assertions = { workspace = true }
[build-dependencies]
react_estree_codegen = { workspace = true }

View File

@@ -0,0 +1,17 @@
# react_estree
This crate is a Rust representation of the [ESTree format](https://github.com/estree/estree/tree/master) and
popular extenions including JSX and (eventually) Flow and TypeScript.
This crate is intended as the main interchange format with outside code. A typical integration with React Compiler
will look as follows:
1. Host Compiler parses into the host AST format.
2. Host Compiler converts into `react_estree`.
3. Host Compiler invokes React Compiler to compile the input, which (conceptually)
returns the resulting code in `react_estree` format.
4. Host Compiler convert back from `react_estree` to its host AST format.
Because React Compiler is intended to support JavaScript-based toolchains, `react_estree` is designed to support
accurate serialization to/from estree-compatible JSON. We may also support the Babel AST format
(a variant of ESTree) as well, depending on demand.

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use react_estree_codegen::estree;
// Example custom build script.
fn main() {
// Re-run if the codegen files change
println!("cargo:rerun-if-changed=../react_estree_codegen/src/codegen.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/lib.rs");
println!("cargo:rerun-if-changed=../react_estree_codegen/src/ecmascript.json");
println!("cargo:rerun-if-changed=../react_estree_codegen");
let src = estree();
let copyright = "
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"
.to_string();
let trimmed_copyright = copyright.trim();
let contents = format!("{trimmed_copyright}\n{src}");
std::fs::write("src/generated.rs", contents).unwrap();
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Binding {
Global,
Module(BindingId),
Local(BindingId),
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct BindingId(u32);
impl BindingId {
pub fn new(value: u32) -> Self {
Self(value)
}
}
impl From<BindingId> for u32 {
fn from(value: BindingId) -> Self {
value.0
}
}

View File

@@ -0,0 +1,528 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 7,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"name": "Component",
"typeAnnotation": null,
"optional": false,
"range": [
9,
18
]
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"name": "props",
"typeAnnotation": null,
"optional": false,
"range": [
19,
24
]
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 7,
"column": 1
}
},
"body": [
{
"type": "VariableDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 12
}
},
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 11
}
},
"init": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 11
}
},
"value": 0,
"range": [
38,
39
],
"raw": "0"
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 7
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
34,
35
]
},
"range": [
34,
39
]
}
],
"range": [
30,
40
]
},
{
"type": "ForStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 5,
"column": 3
}
},
"init": {
"type": "VariableDeclaration",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 7
},
"end": {
"line": 3,
"column": 16
}
},
"kind": "let",
"declarations": [
{
"type": "VariableDeclarator",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 16
}
},
"init": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 15
},
"end": {
"line": 3,
"column": 16
}
},
"value": 0,
"range": [
56,
57
],
"raw": "0"
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 11
},
"end": {
"line": 3,
"column": 12
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
52,
53
]
},
"range": [
52,
57
]
}
],
"range": [
48,
57
]
},
"test": {
"type": "BinaryExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"left": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
59,
60
]
},
"right": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 22
},
"end": {
"line": 3,
"column": 24
}
},
"value": 10,
"range": [
63,
65
],
"raw": "10"
},
"operator": "<",
"range": [
59,
65
]
},
"update": {
"type": "UpdateExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 29
}
},
"operator": "++",
"argument": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 27
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
67,
68
]
},
"prefix": false,
"range": [
67,
70
]
},
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 31
},
"end": {
"line": 5,
"column": 3
}
},
"body": [
{
"type": "ExpressionStatement",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 11
}
},
"expression": {
"type": "AssignmentExpression",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 10
}
},
"operator": "+=",
"left": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 4
},
"end": {
"line": 4,
"column": 5
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
78,
79
]
},
"right": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 4,
"column": 9
},
"end": {
"line": 4,
"column": 10
}
},
"name": "i",
"typeAnnotation": null,
"optional": false,
"range": [
83,
84
]
},
"range": [
78,
84
]
},
"directive": null,
"range": [
78,
85
]
}
],
"range": [
72,
89
]
},
"range": [
43,
89
]
},
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 6,
"column": 2
},
"end": {
"line": 6,
"column": 11
}
},
"argument": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 6,
"column": 9
},
"end": {
"line": 6,
"column": 10
}
},
"name": "x",
"typeAnnotation": null,
"optional": false,
"range": [
99,
100
]
},
"range": [
92,
101
]
}
],
"range": [
26,
103
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
0,
103
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
103
],
"sourceType": "script"
}

View File

@@ -0,0 +1,104 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"body": [
{
"type": "ImportDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"local": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"name": "React",
"typeAnnotation": null,
"optional": false,
"range": [
7,
12
]
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"value": "react",
"range": [
18,
25
],
"raw": "'react'"
},
"attributes": [],
"importKind": "value",
"range": [
0,
26
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
26
],
"sourceType": "module"
}

View File

@@ -0,0 +1,189 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
],
"name": "Component",
"typeAnnotation": null,
"optional": false
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
],
"name": "props",
"typeAnnotation": null,
"optional": false
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
],
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
],
"argument": {
"type": "MemberExpression",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
],
"object": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
],
"name": "props",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
],
"name": "value",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
]
},
"async": false,
"generator": false,
"predicate": null,
"expression": false,
"returnType": null,
"typeParameters": null
}
],
"comments": [],
"errors": []
}

View File

@@ -0,0 +1,351 @@
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"name": "foo",
"typeAnnotation": null,
"optional": false,
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"argument": {
"type": "JSXElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"openingElement": {
"type": "JSXOpeningElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"name": "Foo",
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"name": "Bar",
"range": [
32,
35
]
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"name": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "a",
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"expression": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"value": 10,
"range": [
39,
41
],
"raw": "10"
},
"range": [
38,
42
]
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"name": "Foo",
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"name": "Bar",
"range": [
50,
53
]
},
"range": [
46,
53
]
},
"range": [
44,
54
]
},
"range": [
27,
54
]
},
"range": [
20,
54
]
}
],
"range": [
16,
56
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
1,
56
]
}
],
"comments": [],
"interpreter": null,
"range": [
1,
56
],
"sourceType": "script"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// Manual extensions to generated types
use crate::{
ArrowFunctionExpression, Class, ClassDeclaration, ClassExpression, Function,
FunctionDeclaration, FunctionExpression, ImportDeclarationSpecifier, JSXElementName,
JSXMemberExpression, JSXMemberExpressionOrIdentifier, Pattern, SourceRange, SourceType,
};
/// Sentinel trait to distinguish AST *node* types
pub trait ESTreeNode {}
impl Default for SourceType {
fn default() -> Self {
Self::Module
}
}
impl Pattern {
pub fn range(&self) -> Option<SourceRange> {
match self {
Self::ArrayPattern(pattern) => pattern.range,
Self::AssignmentPattern(pattern) => pattern.range,
Self::Identifier(pattern) => pattern.range,
Self::ObjectPattern(pattern) => pattern.range,
Self::RestElement(pattern) => pattern.range,
}
}
}
impl ImportDeclarationSpecifier {
pub fn range(&self) -> Option<SourceRange> {
match self {
Self::ImportDefaultSpecifier(specifier) => specifier.range,
Self::ImportNamespaceSpecifier(specifier) => specifier.range,
Self::ImportSpecifier(specifier) => specifier.range,
}
}
}
impl JSXElementName {
pub fn root_name(&self) -> &str {
match self {
Self::JSXIdentifier(name) => &name.name,
Self::JSXMemberExpression(name) => name.root_name(),
Self::JSXNamespacedName(name) => &name.namespace.name,
}
}
}
impl JSXMemberExpression {
pub fn root_name(&self) -> &str {
match &self.object {
JSXMemberExpressionOrIdentifier::JSXMemberExpression(object) => object.root_name(),
JSXMemberExpressionOrIdentifier::JSXIdentifier(object) => &object.name,
}
}
}
pub trait IntoFunction: ESTreeNode {
fn function(&self) -> &Function;
fn into_function(self) -> Function;
}
impl IntoFunction for FunctionDeclaration {
fn function(&self) -> &Function {
&self.function
}
fn into_function(self) -> Function {
self.function
}
}
impl IntoFunction for FunctionExpression {
fn function(&self) -> &Function {
&self.function
}
fn into_function(self) -> Function {
self.function
}
}
impl IntoFunction for ArrowFunctionExpression {
fn function(&self) -> &Function {
&self.function
}
fn into_function(self) -> Function {
self.function
}
}
impl ESTreeNode for Function {}
impl IntoFunction for Function {
fn function(&self) -> &Function {
self
}
fn into_function(self) -> Function {
self
}
}
pub trait IntoClass: ESTreeNode {
fn class(&self) -> &Class;
fn into_class(self) -> Class;
}
impl IntoClass for ClassDeclaration {
fn class(&self) -> &Class {
&self.class
}
fn into_class(self) -> Class {
self.class
}
}
impl IntoClass for ClassExpression {
fn class(&self) -> &Class {
&self.class
}
fn into_class(self) -> Class {
self.class
}
}
impl ESTreeNode for Class {}
impl IntoClass for Class {
fn class(&self) -> &Class {
self
}
fn into_class(self) -> Class {
self
}
}

View File

@@ -0,0 +1,295 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use serde::de::Visitor;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JsValue {
Boolean(bool),
Null,
Number(Number),
String(String),
Undefined,
}
impl JsValue {
pub fn is_truthy(&self) -> bool {
match &self {
JsValue::Boolean(value) => *value,
JsValue::Number(value) => value.is_truthy(),
JsValue::String(value) => !value.is_empty(),
JsValue::Null => false,
JsValue::Undefined => false,
}
}
// Partial implementation of loose equality for javascript, returns Some for supported
// cases w the equality result, and None for unsupported cases
pub fn loosely_equals(&self, other: &Self) -> Option<bool> {
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal
match (&self, &other) {
// 1. If Type(x) is Type(y), then
// a. Return IsStrictlyEqual(x, y).
(JsValue::Number(left), JsValue::Number(right)) => Some(left.equals(*right)),
(JsValue::Null, JsValue::Null) => Some(true),
(JsValue::Undefined, JsValue::Undefined) => Some(true),
(JsValue::Boolean(left), JsValue::Boolean(right)) => Some(left == right),
(JsValue::String(left), JsValue::String(right)) => Some(left == right),
// 2. If x is null and y is undefined, return true.
(JsValue::Null, JsValue::Undefined) => Some(true),
// 3. If x is undefined and y is null, return true.
(JsValue::Undefined, JsValue::Null) => Some(true),
_ => None,
}
}
pub fn not_loosely_equals(&self, other: &Self) -> Option<bool> {
self.loosely_equals(other).map(|value| !value)
}
// Complete implementation of strict equality for javascript
pub fn strictly_equals(&self, other: &Self) -> bool {
// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal
match (&self, &other) {
(JsValue::Number(left), JsValue::Number(right)) => left.equals(*right),
(JsValue::Null, JsValue::Null) => true,
(JsValue::Undefined, JsValue::Undefined) => true,
(JsValue::Boolean(left), JsValue::Boolean(right)) => left == right,
(JsValue::String(left), JsValue::String(right)) => left == right,
_ => false,
}
}
pub fn not_strictly_equals(&self, other: &Self) -> bool {
!self.strictly_equals(other)
}
}
impl Serialize for JsValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Boolean(b) => serializer.serialize_bool(*b),
Self::Null => serializer.serialize_none(),
Self::Number(n) => serializer.serialize_f64(n.into()),
Self::String(s) => serializer.serialize_str(s),
Self::Undefined => serializer.serialize_unit(),
}
}
}
impl<'de> Deserialize<'de> for JsValue {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<JsValue, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = JsValue;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("valid primitive JSON value (null, boolean, number, or string")
}
#[inline]
fn visit_bool<E>(self, value: bool) -> Result<JsValue, E> {
Ok(JsValue::Boolean(value))
}
#[inline]
fn visit_i64<E>(self, value: i64) -> Result<JsValue, E> {
if (MIN_SAFE_INT..=MAX_SAFE_INT).contains(&value) {
Ok(JsValue::Number((value as f64).into()))
} else {
panic!("Invalid number")
}
}
#[inline]
fn visit_u64<E>(self, value: u64) -> Result<JsValue, E> {
if value as i64 <= MAX_SAFE_INT {
Ok(JsValue::Number((value as f64).into()))
} else {
panic!("Invalid number")
}
}
#[inline]
fn visit_f64<E>(self, value: f64) -> Result<JsValue, E> {
Ok(JsValue::Number(value.into()))
}
#[inline]
fn visit_str<E>(self, value: &str) -> Result<JsValue, E>
where
E: serde::de::Error,
{
self.visit_string(String::from(value))
}
#[inline]
fn visit_string<E>(self, value: String) -> Result<JsValue, E> {
Ok(JsValue::String(value))
}
#[inline]
fn visit_none<E>(self) -> Result<JsValue, E> {
Ok(JsValue::Null)
}
#[inline]
fn visit_some<D>(self, deserializer: D) -> Result<JsValue, D::Error>
where
D: serde::Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
}
#[inline]
fn visit_unit<E>(self) -> Result<JsValue, E> {
Ok(JsValue::Undefined)
}
}
deserializer.deserialize_any(ValueVisitor)
}
}
/// Represents a JavaScript Number as its binary representation so that
/// -1 == -1, NaN == Nan etc.
/// Note: NaN is *always* represented as the f64::NAN constant to allow
/// comparison of NaNs.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Debug, Hash)]
pub struct Number(u64);
pub const MAX_SAFE_INT: i64 = 9007199254740991;
pub const MIN_SAFE_INT: i64 = -9007199254740991;
impl From<f64> for Number {
fn from(value: f64) -> Self {
if value.is_nan() {
Self(f64::NAN.to_bits())
} else {
Self(value.to_bits())
}
}
}
impl From<u32> for Number {
fn from(value: u32) -> Self {
f64::from(value).into()
}
}
impl From<Number> for f64 {
fn from(number: Number) -> Self {
let value = f64::from_bits(number.0);
assert!(!f64::is_nan(value) || number.0 == f64::NAN.to_bits());
value
}
}
impl From<&Number> for f64 {
fn from(number: &Number) -> Self {
let value = f64::from_bits(number.0);
assert!(!f64::is_nan(value) || number.0 == f64::NAN.to_bits());
value
}
}
impl Number {
pub fn equals(self, other: Self) -> bool {
f64::from(self) == f64::from(other)
}
pub fn not_equals(self, other: Self) -> bool {
!self.equals(other)
}
pub fn is_truthy(self) -> bool {
let value = f64::from(self);
!(self.0 == f64::NAN.to_bits() || value == 0.0 || value == -0.0)
}
}
impl std::ops::Add for Number {
type Output = Number;
fn add(self, rhs: Self) -> Self::Output {
let result = f64::from(self) + f64::from(rhs);
Self::from(result)
}
}
impl std::ops::Sub for Number {
type Output = Number;
fn sub(self, rhs: Self) -> Self::Output {
let result = f64::from(self) - f64::from(rhs);
Self::from(result)
}
}
impl std::ops::Mul for Number {
type Output = Number;
fn mul(self, rhs: Self) -> Self::Output {
let result = f64::from(self) * f64::from(rhs);
Self::from(result)
}
}
impl std::ops::Div for Number {
type Output = Number;
fn div(self, rhs: Self) -> Self::Output {
let result = f64::from(self) / f64::from(rhs);
Self::from(result)
}
}
impl Serialize for Number {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_f64(self.into())
}
}
impl<'de> Deserialize<'de> for Number {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ValueVisitor;
impl<'de> Visitor<'de> for ValueVisitor {
type Value = Number;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("value JavaScript number value")
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}
}
deserializer.deserialize_any(ValueVisitor)
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
mod binding;
mod generated;
mod generated_extensions;
mod js_value;
mod range;
mod visit;
pub use binding::{Binding, BindingId};
pub use generated::*;
pub use generated_extensions::*;
pub use js_value::{JsValue, Number};
pub use range::SourceRange;
pub use visit::*;
#[cfg(test)]
mod tests {
use insta::{assert_snapshot, glob};
use super::*;
#[test]
fn fixtures() {
glob!("fixtures/**.json", |path| {
println!("{:?}", path);
let input = std::fs::read_to_string(path).unwrap();
let ast: Program = serde_json::from_str(&input).unwrap();
let serialized = serde_json::to_string_pretty(&ast).unwrap();
assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{serialized}"));
});
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::num::NonZeroU32;
use serde::ser::SerializeTuple;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Copy, Clone, Debug, PartialEq, PartialOrd, Hash)]
pub struct SourceRange {
pub start: u32,
pub end: NonZeroU32,
}
// ESTree and Babel store the `range` as `[start, end]`, so we customize
// the serialization to use a tuple representation.
impl Serialize for SourceRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&self.start)?;
tuple.serialize_element(&self.end)?;
tuple.end()
}
}

View File

@@ -0,0 +1,213 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/import.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"body": [
{
"type": "ImportDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"local": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"name": "React",
"typeAnnotation": null,
"optional": false,
"range": [
7,
12
]
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"value": "react",
"range": [
18,
25
],
"raw": "'react'"
},
"attributes": [],
"importKind": "value",
"range": [
0,
26
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
26
],
"sourceType": "module"
}
Output:
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "React",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"value": "react",
"raw": "'react'",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"range": [
18,
25
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}

View File

@@ -0,0 +1,379 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/simple.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
],
"name": "Component",
"typeAnnotation": null,
"optional": false
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
],
"name": "props",
"typeAnnotation": null,
"optional": false
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
],
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
],
"argument": {
"type": "MemberExpression",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
],
"object": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
],
"name": "props",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
],
"name": "value",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
]
},
"async": false,
"generator": false,
"predicate": null,
"expression": false,
"returnType": null,
"typeParameters": null
}
],
"comments": [],
"errors": []
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "Component",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
]
},
"params": [
{
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
]
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
]
},
"property": {
"type": "Identifier",
"name": "value",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
]
},
"computed": false,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
]
},
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
]
}
],
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}

View File

@@ -0,0 +1,708 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/test.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"name": "foo",
"typeAnnotation": null,
"optional": false,
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"argument": {
"type": "JSXElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"openingElement": {
"type": "JSXOpeningElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"name": "Foo",
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"name": "Bar",
"range": [
32,
35
]
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"name": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "a",
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"expression": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"value": 10,
"range": [
39,
41
],
"raw": "10"
},
"range": [
38,
42
]
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"name": "Foo",
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"name": "Bar",
"range": [
50,
53
]
},
"range": [
46,
53
]
},
"range": [
44,
54
]
},
"range": [
27,
54
]
},
"range": [
20,
54
]
}
],
"range": [
16,
56
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
1,
56
]
}
],
"comments": [],
"interpreter": null,
"range": [
1,
56
],
"sourceType": "script"
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "foo",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
32,
35
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"name": {
"type": "JSXIdentifier",
"name": "a",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"expression": {
"type": "Literal",
"value": 10.0,
"raw": "10",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"range": [
39,
41
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
38,
42
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
50,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
46,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
44,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
27,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
20,
54
]
}
],
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
16,
56
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}
],
"sourceType": "script",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}

View File

@@ -0,0 +1,213 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/import.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"body": [
{
"type": "ImportDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"local": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"name": "React",
"typeAnnotation": null,
"optional": false,
"range": [
7,
12
]
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"value": "react",
"range": [
18,
25
],
"raw": "'react'"
},
"attributes": [],
"importKind": "value",
"range": [
0,
26
]
}
],
"comments": [],
"interpreter": null,
"range": [
0,
26
],
"sourceType": "module"
}
Output:
{
"type": "Program",
"body": [
{
"type": "ImportDeclaration",
"specifiers": [
{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "React",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 12
}
},
"range": [
7,
12
]
}
],
"source": {
"type": "Literal",
"value": "react",
"raw": "'react'",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 18
},
"end": {
"line": 1,
"column": 25
}
},
"range": [
18,
25
]
},
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 26
}
},
"range": [
0,
26
]
}

View File

@@ -0,0 +1,379 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/simple.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
],
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
],
"name": "Component",
"typeAnnotation": null,
"optional": false
},
"params": [
{
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
],
"name": "props",
"typeAnnotation": null,
"optional": false
}
],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
],
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
],
"argument": {
"type": "MemberExpression",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
],
"object": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
],
"name": "props",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
],
"name": "value",
"typeAnnotation": null,
"optional": false
},
"computed": false
}
}
]
},
"async": false,
"generator": false,
"predicate": null,
"expression": false,
"returnType": null,
"typeParameters": null
}
],
"comments": [],
"errors": []
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "Component",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 9
},
"end": {
"line": 1,
"column": 18
}
},
"range": [
9,
18
]
},
"params": [
{
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 1,
"column": 24
}
},
"range": [
19,
24
]
}
],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "props",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 14
}
},
"range": [
37,
42
]
},
"property": {
"type": "Identifier",
"name": "value",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
43,
48
]
},
"computed": false,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 20
}
},
"range": [
37,
48
]
},
"loc": {
"source": null,
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 21
}
},
"range": [
30,
49
]
}
],
"loc": {
"source": null,
"start": {
"line": 1,
"column": 26
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
26,
51
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}
],
"sourceType": "module",
"loc": {
"source": null,
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"range": [
0,
51
]
}

View File

@@ -0,0 +1,708 @@
---
source: crates/react_estree/src/lib.rs
expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{serialized}\")"
input_file: crates/react_estree/src/fixtures/test.json
---
Input:
{
"type": "Program",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"id": {
"type": "Identifier",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"name": "foo",
"typeAnnotation": null,
"optional": false,
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"body": [
{
"type": "ReturnStatement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"argument": {
"type": "JSXElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"openingElement": {
"type": "JSXOpeningElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"name": "Foo",
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"name": "Bar",
"range": [
32,
35
]
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"name": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"name": "a",
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"expression": {
"type": "Literal",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"value": 10,
"range": [
39,
41
],
"raw": "10"
},
"range": [
38,
42
]
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"name": {
"type": "JSXMemberExpression",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"object": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"name": "Foo",
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"name": "Bar",
"range": [
50,
53
]
},
"range": [
46,
53
]
},
"range": [
44,
54
]
},
"range": [
27,
54
]
},
"range": [
20,
54
]
}
],
"range": [
16,
56
]
},
"typeParameters": null,
"returnType": null,
"predicate": null,
"generator": false,
"async": false,
"range": [
1,
56
]
}
],
"comments": [],
"interpreter": null,
"range": [
1,
56
],
"sourceType": "script"
}
Output:
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "foo",
"typeAnnotation": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 9
},
"end": {
"line": 2,
"column": 12
}
},
"range": [
10,
13
]
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 13
}
},
"range": [
28,
31
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 14
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
32,
35
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 10
},
"end": {
"line": 3,
"column": 17
}
},
"range": [
28,
35
]
},
"attributes": [
{
"type": "JSXAttribute",
"name": {
"type": "JSXIdentifier",
"name": "a",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 19
}
},
"range": [
36,
37
]
},
"value": {
"type": "JSXExpressionContainer",
"expression": {
"type": "Literal",
"value": 10.0,
"raw": "10",
"regex": null,
"bigint": null,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 21
},
"end": {
"line": 3,
"column": 23
}
},
"range": [
39,
41
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 20
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
38,
42
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 18
},
"end": {
"line": 3,
"column": 24
}
},
"range": [
36,
42
]
}
],
"selfClosing": false,
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 26
}
},
"range": [
27,
44
]
},
"children": [],
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXMemberExpression",
"object": {
"type": "JSXIdentifier",
"name": "Foo",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 31
}
},
"range": [
46,
49
]
},
"property": {
"type": "JSXIdentifier",
"name": "Bar",
"loc": {
"source": null,
"start": {
"line": 3,
"column": 32
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
50,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 28
},
"end": {
"line": 3,
"column": 35
}
},
"range": [
46,
53
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 26
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
44,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 9
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
27,
54
]
},
"loc": {
"source": null,
"start": {
"line": 3,
"column": 2
},
"end": {
"line": 3,
"column": 36
}
},
"range": [
20,
54
]
}
],
"loc": {
"source": null,
"start": {
"line": 2,
"column": 15
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
16,
56
]
},
"generator": false,
"async": false,
"loc": null,
"range": null,
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}
],
"sourceType": "script",
"loc": {
"source": null,
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 4,
"column": 1
}
},
"range": [
1,
56
]
}

View File

@@ -0,0 +1,537 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use crate::{
AssignmentPropertyOrRestElement, AssignmentTarget, Class, ClassItem, ClassPrivateProperty,
ClassProperty, Declaration, DeclarationOrExpression, ExportAllDeclaration,
ExportDefaultDeclaration, ExportNamedDeclaration, Expression, ExpressionOrPrivateIdentifier,
ExpressionOrSpread, ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody,
FunctionDeclaration, Identifier, ImportDeclaration, ImportDeclarationSpecifier,
ImportOrExportDeclaration, Literal, MethodDefinition, ModuleItem, Pattern, PrivateIdentifier,
PrivateName, Program, Statement, StaticBlock, Super, SwitchCase, VariableDeclarator, _Literal,
};
/// Trait for visiting an estree
#[allow(non_camel_case_types)]
#[deprecated]
pub trait Visitor_DEPRECATED<'ast> {
fn visit_lvalue<F>(&mut self, f: F)
where
F: FnOnce(&mut Self),
{
f(self);
}
fn visit_rvalue<F>(&mut self, f: F)
where
F: FnOnce(&mut Self),
{
f(self);
}
fn visit_program(&mut self, program: &'ast Program) {
self.default_visit_program(program)
}
fn default_visit_program(&mut self, program: &'ast Program) {
for item in &program.body {
self.visit_module_item(item);
}
}
fn visit_function(&mut self, function: &'ast Function) {
self.default_visit_function(function);
}
fn default_visit_function(&mut self, function: &'ast Function) {
self.visit_lvalue(|visitor| {
for param in &function.params {
visitor.visit_pattern(param);
}
});
match &function.body {
Some(FunctionBody::BlockStatement(body)) => {
for stmt in &body.body {
self.visit_statement(stmt)
}
}
Some(FunctionBody::Expression(body)) => self.visit_expression(body),
None => {}
}
}
fn visit_module_item(&mut self, item: &'ast ModuleItem) {
match item {
ModuleItem::Statement(item) => self.visit_statement(item),
ModuleItem::ImportOrExportDeclaration(item) => {
self.visit_import_or_export_declaration(item)
}
}
}
fn visit_import_or_export_declaration(&mut self, declaration: &'ast ImportOrExportDeclaration) {
match declaration {
ImportOrExportDeclaration::ImportDeclaration(declaration) => {
self.visit_import_declaration(declaration);
}
ImportOrExportDeclaration::ExportAllDeclaration(declaration) => {
self.visit_export_all_declaration(declaration);
}
ImportOrExportDeclaration::ExportDefaultDeclaration(declaration) => {
self.visit_export_default_declaration(declaration);
}
ImportOrExportDeclaration::ExportNamedDeclaration(declaration) => {
self.visit_export_named_declaration(declaration);
}
}
}
fn visit_import_declaration(&mut self, declaration: &'ast ImportDeclaration) {
self.visit_lvalue(|visitor| {
for specifier in &declaration.specifiers {
visitor.visit_import_declaration_specifier(specifier);
}
});
self.visit_import_source(&declaration.source);
}
fn visit_export_all_declaration(&mut self, declaration: &'ast ExportAllDeclaration) {
self.visit_export_source(&declaration.source);
}
fn visit_export_default_declaration(&mut self, declaration: &'ast ExportDefaultDeclaration) {
match &declaration.declaration {
DeclarationOrExpression::Declaration(declaration) => {
self.visit_declaration(declaration)
}
DeclarationOrExpression::Expression(declaration) => self.visit_expression(declaration),
}
}
fn visit_export_named_declaration(&mut self, declaration: &'ast ExportNamedDeclaration) {
if let Some(declaration) = &declaration.declaration {
self.visit_declaration(declaration)
}
if let Some(source) = &declaration.source {
self.visit_export_source(source);
}
}
fn visit_import_declaration_specifier(&mut self, specifier: &'ast ImportDeclarationSpecifier) {
match specifier {
ImportDeclarationSpecifier::ImportSpecifier(specifier) => {
self.visit_identifier(&specifier.local);
}
ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => {
self.visit_identifier(&specifier.local);
}
ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => {
self.visit_identifier(&specifier.local);
}
}
}
fn visit_declaration(&mut self, declaration: &'ast Declaration) {
self.default_visit_declaration(declaration);
}
fn default_visit_declaration(&mut self, declaration: &'ast Declaration) {
match declaration {
Declaration::ClassDeclaration(declaration) => {
self.visit_class(&declaration.class);
}
Declaration::FunctionDeclaration(declaration) => {
self.visit_function_declaration(declaration);
}
Declaration::VariableDeclaration(declaration) => {
for declarator in &declaration.declarations {
self.visit_variable_declarator(declarator)
}
}
Declaration::TSTypeAliasDeclaration(_declaration) => {
todo!("visit TSTypeAliasDeclaration")
}
}
}
fn visit_function_declaration(&mut self, declaration: &'ast FunctionDeclaration) {
self.visit_function(&declaration.function);
}
fn visit_statement(&mut self, stmt: &'ast Statement) {
self.default_visit_statement(stmt);
}
fn default_visit_statement(&mut self, stmt: &'ast Statement) {
match stmt {
Statement::BlockStatement(stmt) => {
for stmt in &stmt.body {
self.visit_statement(stmt)
}
}
Statement::BreakStatement(_stmt) => {
// todo
}
Statement::ContinueStatement(_stmt) => {
// todo
}
Statement::DebuggerStatement(_stmt) => {
// todo
}
Statement::ClassDeclaration(stmt) => {
self.visit_class(&stmt.class);
}
Statement::DoWhileStatement(stmt) => {
self.visit_statement(&stmt.body);
self.visit_expression(&stmt.test);
}
Statement::EmptyStatement(_stmt) => {
// nothing to do
}
Statement::ExpressionStatement(stmt) => {
self.visit_expression(&stmt.expression);
}
Statement::ForInStatement(stmt) => {
self.visit_for_in_init(&stmt.left);
self.visit_expression(&stmt.right);
self.visit_statement(&stmt.body);
}
Statement::ForOfStatement(stmt) => {
self.visit_for_in_init(&stmt.left);
self.visit_expression(&stmt.right);
self.visit_statement(&stmt.body);
}
Statement::ForStatement(stmt) => {
if let Some(init) = &stmt.init {
self.visit_for_init(init);
}
if let Some(test) = &stmt.test {
self.visit_expression(test);
}
if let Some(update) = &stmt.update {
self.visit_expression(update);
}
self.visit_statement(&stmt.body);
}
Statement::FunctionDeclaration(stmt) => {
self.visit_function_declaration(stmt);
}
Statement::IfStatement(stmt) => {
self.visit_expression(&stmt.test);
self.visit_statement(&stmt.consequent);
if let Some(alternate) = &stmt.alternate {
self.visit_statement(alternate);
}
}
Statement::LabeledStatement(stmt) => {
self.visit_statement(&stmt.body);
}
Statement::ReturnStatement(stmt) => {
if let Some(argument) = &stmt.argument {
self.visit_expression(argument);
}
}
Statement::SwitchStatement(stmt) => {
self.visit_expression(&stmt.discriminant);
for case_ in &stmt.cases {
self.visit_case(case_);
}
}
Statement::ThrowStatement(stmt) => {
self.visit_expression(&stmt.argument);
}
Statement::TryStatement(stmt) => {
for item in &stmt.block.body {
self.visit_statement(item);
}
if let Some(handler) = &stmt.handler {
if let Some(param) = &handler.param {
self.visit_lvalue(|visitor| visitor.visit_pattern(param));
}
for item in &handler.body.body {
self.visit_statement(item);
}
}
if let Some(finalizer) = &stmt.finalizer {
for item in &finalizer.body {
self.visit_statement(item);
}
}
}
Statement::VariableDeclaration(stmt) => {
for decl in &stmt.declarations {
self.visit_variable_declarator(decl);
}
}
Statement::WhileStatement(stmt) => {
self.visit_expression(&stmt.test);
self.visit_statement(&stmt.body);
}
Statement::WithStatement(stmt) => {
self.visit_expression(&stmt.object);
self.visit_statement(&stmt.body);
}
Statement::TSTypeAliasDeclaration(_stmt) => {
todo!("visit TSTypeAliasDeclaration")
}
}
}
fn visit_class(&mut self, class: &'ast Class) {
if let Some(id) = &class.id {
self.visit_identifier(id)
}
if let Some(super_class) = &class.super_class {
self.visit_expression(super_class);
}
for item in &class.body.body {
match item {
ClassItem::MethodDefinition(item) => self.visit_method_definition(item),
ClassItem::ClassProperty(item) => {
self.visit_class_property(item);
}
ClassItem::ClassPrivateProperty(item) => {
self.visit_class_private_property(item);
}
ClassItem::StaticBlock(item) => {
self.visit_static_block(item);
}
}
}
}
fn visit_class_property(&mut self, property: &'ast ClassProperty) {
self.visit_expression(&property.key);
if let Some(value) = &property.value {
self.visit_expression(value)
}
}
fn visit_class_private_property(&mut self, property: &'ast ClassPrivateProperty) {
match &property.key {
ExpressionOrPrivateIdentifier::Expression(key) => self.visit_expression(key),
ExpressionOrPrivateIdentifier::PrivateIdentifier(key) => {
self.visit_private_identifier(key)
}
ExpressionOrPrivateIdentifier::PrivateName(key) => self.visit_private_name(key),
}
if let Some(value) = &property.value {
self.visit_expression(value)
}
}
fn visit_static_block(&mut self, property: &'ast StaticBlock) {
for stmt in &property.body {
self.visit_statement(stmt)
}
}
fn visit_method_definition(&mut self, method: &'ast MethodDefinition) {
self.default_visit_method_definition(method);
}
fn default_visit_method_definition(&mut self, method: &'ast MethodDefinition) {
self.visit_expression(&method.key);
self.visit_function(&method.value.function);
}
fn visit_case(&mut self, case_: &'ast SwitchCase) {
if let Some(test) = &case_.test {
self.visit_expression(test);
}
for stmt in &case_.consequent {
self.visit_statement(stmt)
}
}
fn visit_for_init(&mut self, init: &'ast ForInit) {
match init {
ForInit::Expression(init) => {
self.visit_expression(init);
}
ForInit::VariableDeclaration(init) => {
for decl in &init.declarations {
self.visit_variable_declarator(decl);
}
}
}
}
fn visit_for_in_init(&mut self, init: &'ast ForInInit) {
match init {
ForInInit::Pattern(init) => {
self.visit_pattern(init);
}
ForInInit::VariableDeclaration(init) => {
for decl in &init.declarations {
self.visit_variable_declarator(decl);
}
}
}
}
fn visit_pattern(&mut self, pattern: &'ast Pattern) {
match pattern {
Pattern::Identifier(pattern) => self.visit_identifier(pattern),
Pattern::ArrayPattern(pattern) => {
for element in &pattern.elements {
if let Some(element) = element {
self.visit_pattern(element);
}
}
}
Pattern::ObjectPattern(pattern) => {
for property in &pattern.properties {
match property {
AssignmentPropertyOrRestElement::AssignmentProperty(property) => {
self.visit_pattern(&property.value);
}
AssignmentPropertyOrRestElement::RestElement(property) => {
self.visit_pattern(&property.argument);
}
}
}
}
Pattern::RestElement(pattern) => self.visit_pattern(&pattern.argument),
Pattern::AssignmentPattern(pattern) => {
self.visit_pattern(&pattern.left);
self.visit_rvalue(|visitor| {
visitor.visit_expression(&pattern.right);
});
}
}
}
fn visit_variable_declarator(&mut self, decl: &'ast VariableDeclarator) {
self.visit_lvalue(|visitor| {
visitor.visit_pattern(&decl.id);
});
if let Some(init) = &decl.init {
self.visit_expression(init);
}
}
fn visit_assignment_target(&mut self, target: &'ast AssignmentTarget) {
match target {
AssignmentTarget::Expression(target) => {
self.visit_expression(target);
}
AssignmentTarget::Pattern(target) => self.visit_pattern(target),
}
}
fn visit_expression(&mut self, expr: &'ast Expression) {
self.default_visit_expression(expr);
}
fn default_visit_expression(&mut self, expr: &'ast Expression) {
match expr {
Expression::ArrayExpression(expr) => {
for item in &expr.elements {
match item {
Some(ExpressionOrSpread::SpreadElement(item)) => {
self.visit_expression(&item.argument)
}
Some(ExpressionOrSpread::Expression(item)) => self.visit_expression(item),
_ => {}
}
}
}
Expression::AssignmentExpression(expr) => {
self.visit_lvalue(|visitor| visitor.visit_assignment_target(&expr.left));
self.visit_expression(&expr.right);
}
Expression::BinaryExpression(expr) => {
self.visit_expression(&expr.left);
self.visit_expression(&expr.right);
}
Expression::Identifier(expr) => {
self.visit_identifier(expr);
}
Expression::Literal(expr) => self.visit_literal(expr),
Expression::FunctionExpression(expr) => self.visit_function(&expr.function),
Expression::ArrowFunctionExpression(expr) => self.visit_function(&expr.function),
Expression::MemberExpression(expr) => {
match &expr.object {
ExpressionOrSuper::Super(object) => self.visit_super(object),
ExpressionOrSuper::Expression(object) => self.visit_expression(object),
};
if !expr.is_computed {
match &expr.property {
ExpressionOrPrivateIdentifier::Expression(property) => {
self.visit_expression(property)
}
ExpressionOrPrivateIdentifier::PrivateIdentifier(property) => {
self.visit_private_identifier(property);
}
ExpressionOrPrivateIdentifier::PrivateName(property) => {
self.visit_private_name(property);
}
}
}
}
Expression::CallExpression(expr) => {
match &expr.callee {
ExpressionOrSuper::Expression(callee) => self.visit_expression(callee),
ExpressionOrSuper::Super(callee) => self.visit_super(callee),
}
for arg in &expr.arguments {
match arg {
ExpressionOrSpread::Expression(arg) => self.visit_expression(arg),
ExpressionOrSpread::SpreadElement(arg) => {
self.visit_expression(&arg.argument)
}
}
}
}
Expression::UpdateExpression(expr) => {
self.visit_expression(&expr.argument);
}
Expression::BooleanLiteral(_)
| Expression::NullLiteral(_)
| Expression::StringLiteral(_)
| Expression::NumericLiteral(_) => {
// no-op
}
_ => {
todo!("{:#?}", expr)
}
}
}
fn visit_super(&mut self, _super: &'ast Super) {
todo!("Implement visit_super")
}
fn visit_private_identifier(&mut self, _identifier: &'ast PrivateIdentifier) {
todo!("Implement visit_private_identifier()")
}
fn visit_private_name(&mut self, _identifier: &'ast PrivateName) {
todo!("Implement visit_private_name()")
}
fn visit_identifier(&mut self, _identifier: &'ast Identifier) {
todo!("Implement visit_identifier()")
}
fn visit_import_source(&mut self, literal: &'ast _Literal) {
self.visit_any_literal(literal);
}
fn visit_export_source(&mut self, literal: &'ast _Literal) {
self.visit_any_literal(literal);
}
fn visit_any_literal(&mut self, _literal: &'ast _Literal) {
todo!("Implement visit_any_literal()")
}
fn visit_literal(&mut self, _literal: &'ast Literal) {
todo!("Implement visit_literal()")
}
}

View File

@@ -0,0 +1,21 @@
[package]
name = "react_estree_codegen"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
indexmap = { workspace = true }
prettyplease = { workspace = true }
quote = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
syn = { workspace = true }

View File

@@ -0,0 +1,4 @@
# react_estree_codegen
This crate is a build dependency for `react_estree`, and contains codegen logic to produce Rust code to describe the ESTree format
given a JSON schema.

View File

@@ -0,0 +1,969 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
use std::collections::HashSet;
use indexmap::IndexMap;
use quote::__private::TokenStream;
use quote::{format_ident, quote};
use serde::{Deserialize, Serialize};
use syn::Type;
/// Returns prettyplease-formatted Rust source for estree
pub fn estree() -> String {
let src = include_str!("./ecmascript.json");
let grammar: Grammar = serde_json::from_str(src).unwrap();
let raw = grammar.codegen().to_string();
let parsed = syn::parse_file(&raw).unwrap();
format!(
"// {}generated\n#![cfg_attr(rustfmt, rustfmt_skip)]\n{}",
'\u{0040}',
prettyplease::unparse(&parsed)
)
}
/// Returns prettyplease-formatted Rust source for converting HermesParser results
/// into estree
pub fn estree_hermes() -> String {
let src = include_str!("./ecmascript.json");
let grammar: Grammar = serde_json::from_str(src).unwrap();
let raw = grammar.codegen_hermes().to_string();
let parsed = syn::parse_file(&raw).unwrap();
format!(
"// {}generated\n#![cfg_attr(rustfmt, rustfmt_skip)]\n{}",
'\u{0040}',
prettyplease::unparse(&parsed)
)
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Grammar {
pub objects: IndexMap<String, Object>,
pub nodes: IndexMap<String, Node>,
pub enums: IndexMap<String, Enum>,
pub operators: IndexMap<String, Operator>,
}
impl Grammar {
pub fn codegen(self) -> TokenStream {
let object_defs: Vec<_> = self
.objects
.iter()
.map(|(name, object)| object.codegen(name))
.collect();
let object_visitors: Vec<_> = self
.objects
.iter()
.filter_map(|(name, object)| {
if object.visitor {
Some(object.codegen_visitor(name, &self))
} else {
None
}
})
.collect();
let node_defs: Vec<_> = self
.nodes
.iter()
.map(|(name, node)| node.codegen(name))
.collect();
let node_visitors: Vec<_> = self
.nodes
.iter()
.map(|(name, node)| node.codegen_visitor(name, &self))
.collect();
let enum_defs: Vec<_> = self
.enums
.iter()
.map(|(name, enum_)| enum_.codegen(name, &self.enums))
.collect();
let enum_visitors: Vec<_> = self
.enums
.iter()
.map(|(name, enum_)| enum_.codegen_visitor(name))
.collect();
let operator_defs: Vec<_> = self
.operators
.iter()
.map(|(name, operator)| operator.codegen(name))
.collect();
quote! {
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(non_snake_case)]
#![allow(clippy::enum_variant_names)]
use std::num::NonZeroU32;
use serde::ser::{Serializer, SerializeMap};
use serde::{Serialize,Deserialize};
use crate::{JsValue, Binding, SourceRange, Number, ESTreeNode};
#(#object_defs)*
#(#node_defs)*
#(#enum_defs)*
#(#operator_defs)*
pub trait Visitor {
#(#object_visitors)*
#(#node_visitors)*
#(#enum_visitors)*
}
}
}
pub fn codegen_hermes(self) -> TokenStream {
let nodes: Vec<_> = self
.nodes
.iter()
.filter(|(_, node)| !node.skip_hermes_codegen)
.map(|(name, node)| node.codegen_hermes(name))
.collect();
let enums: Vec<_> = self
.enums
.iter()
.map(|(name, enum_)| enum_.codegen_hermes(name, &self))
.collect();
let operators: Vec<_> = self
.operators
.iter()
.map(|(name, operator)| operator.codegen_hermes(name))
.collect();
quote! {
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(clippy::enum_variant_names)]
use react_estree::*;
use hermes::parser::{NodePtr, NodeKind, NodeLabel };
use hermes::utf::{utf8_with_surrogates_to_string};
use crate::generated_extension::*;
#(#nodes)*
#(#enums)*
#(#operators)*
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Object {
#[serde(default)]
pub fields: IndexMap<String, Field>,
#[serde(default)]
pub visitor: bool,
}
impl Object {
pub fn codegen(&self, name: &str) -> TokenStream {
let name = format_ident!("{}", name);
let fields: Vec<_> = self
.fields
.iter()
.map(|(name, field)| field.codegen(name))
.collect();
quote! {
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct #name {
#(#fields),*
}
}
}
pub fn codegen_visitor(&self, name: &str, grammar: &Grammar) -> TokenStream {
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(name));
let name = format_ident!("{}", name);
let field_visitors: Vec<_> = self
.fields
.iter()
.filter_map(|(name, field)| {
let (type_name_str, type_kind) = parse_type(&field.type_).unwrap();
if !grammar.nodes.contains_key(&type_name_str)
&& !grammar.enums.contains_key(&type_name_str)
{
return None;
}
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(&type_name_str));
let field_name = format_ident!("{}", name);
Some(match type_kind {
TypeKind::Named => {
quote! {
self.#visitor_name(&ast.#field_name);
}
}
TypeKind::Option => {
quote! {
if let Some(#field_name) = &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::Vec => {
quote! {
for #field_name in &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::VecOfOption => {
quote! {
for #field_name in &ast.#field_name {
if let Some(#field_name) = #field_name {
self.#visitor_name(#field_name);
}
}
}
}
})
})
.collect();
quote! {
fn #visitor_name(&mut self, ast: &#name) {
#(#field_visitors)*
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Node {
#[serde(default)]
#[serde(rename = "type")]
pub type_: Option<String>,
#[serde(default)]
pub fields: IndexMap<String, Field>,
#[serde(default)]
pub skip_hermes_codegen: bool,
#[serde(default)]
pub skip_hermes_enum_variant: bool,
#[serde(default)]
#[serde(rename = "TODO")]
pub todo: Option<String>,
}
impl Node {
pub fn codegen(&self, name: &str) -> TokenStream {
let name_str = name;
let name = format_ident!("{}", name);
let fields: Vec<_> = self
.fields
.iter()
.map(|(name, field)| field.codegen_node(name))
.collect();
let type_serializer = if let Some(type_) = &self.type_ {
quote! {
state.serialize_entry("type", #type_)?;
}
} else {
quote! {
state.serialize_entry("type", #name_str)?;
}
};
let mut field_serializers = Vec::with_capacity(self.fields.len()); // type, loc, range
for (field_name_str, field) in &self.fields {
if field.skip {
continue;
}
let field_name = format_ident!("{}", field_name_str);
let serialized_field_name = field.rename.as_ref().unwrap_or(field_name_str);
let serializer = if field.flatten {
quote! {
Serialize::serialize(&self.#field_name, serde::__private::ser::FlatMapSerializer(&mut state))?;
}
} else {
quote! {
state.serialize_entry(#serialized_field_name, &self.#field_name)?;
}
};
field_serializers.push(serializer);
}
quote! {
#[derive(Deserialize, Clone, Debug)]
pub struct #name {
#(#fields,)*
#[serde(default)]
pub loc: Option<SourceLocation>,
#[serde(default)]
pub range: Option<SourceRange>,
}
impl ESTreeNode for #name {}
impl Serialize for #name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_map(None)?;
#type_serializer
#(#field_serializers)*
state.serialize_entry("loc", &self.loc)?;
state.serialize_entry("range", &self.range)?;
state.end()
}
}
}
}
pub fn codegen_visitor(&self, name: &str, grammar: &Grammar) -> TokenStream {
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(name));
let name = format_ident!("{}", name);
let field_visitors: Vec<_> = self
.fields
.iter()
.filter_map(|(name, field)| {
let (type_name_str, type_kind) = parse_type(&field.type_).unwrap();
if (!grammar.objects.contains_key(&type_name_str)
|| !grammar.objects.get(&type_name_str).unwrap().visitor)
&& !grammar.nodes.contains_key(&type_name_str)
&& !grammar.enums.contains_key(&type_name_str)
{
return None;
}
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(&type_name_str));
let field_name = format_ident!("{}", name);
Some(match type_kind {
TypeKind::Named => {
quote! {
self.#visitor_name(&ast.#field_name);
}
}
TypeKind::Option => {
quote! {
if let Some(#field_name) = &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::Vec => {
quote! {
for #field_name in &ast.#field_name {
self.#visitor_name(#field_name);
}
}
}
TypeKind::VecOfOption => {
quote! {
for #field_name in &ast.#field_name {
if let Some(#field_name) = #field_name {
self.#visitor_name(#field_name);
}
}
}
}
})
})
.collect();
quote! {
fn #visitor_name(&mut self, ast: &#name) {
#(#field_visitors)*
}
}
}
pub fn codegen_hermes(&self, name: &str) -> TokenStream {
let name_str = name;
let name = format_ident!("{}", name);
let field_names: Vec<_> = self
.fields
.iter()
.map(|(name, _field)| format_ident!("{}", name))
.collect();
let fields: Vec<_> = self
.fields
.iter()
.map(|(name, field)| {
let (type_name_str, type_kind) = parse_type(&field.type_).unwrap();
let camelcase_name = field.rename.as_ref().unwrap_or(name);
let field_name = format_ident!("{}", name);
let helper = format_ident!("hermes_get_{}_{}", name_str, camelcase_name);
let type_name = format_ident!("{}", type_name_str);
if field.skip || field.hermes_default {
return quote! {
let #field_name = Default::default();
};
}
if let Some(convert_with) = &field.hermes_convert_with {
let convert_with = format_ident!("{}", convert_with);
return quote! {
let #field_name = #convert_with(cx, unsafe { hermes::parser::#helper(node) } );
}
}
match type_kind {
TypeKind::Named => {
match type_name_str.as_ref() {
"bool" => {
quote! {
let #field_name = unsafe { hermes::parser::#helper(node) };
}
}
"Number" => {
quote! {
let #field_name = convert_number(unsafe { hermes::parser::#helper(node) });
}
}
"String" => {
quote! {
let #field_name = convert_string(cx, unsafe { hermes::parser::#helper(node) });
}
}
_ => {
quote! {
let #field_name = #type_name::convert(cx, unsafe { hermes::parser::#helper(node) });
}
}
}
}
TypeKind::Option => {
match type_name_str.as_ref() {
"String" => {
quote! {
let #field_name = convert_option_string(cx, unsafe { hermes::parser::#helper(node) });
}
}
_ => {
quote! {
let #field_name = convert_option(unsafe { hermes::parser::#helper(node) }, |node| #type_name::convert(cx, node));
}
}
}
}
TypeKind::Vec => {
quote! {
let #field_name = convert_vec(unsafe { hermes::parser::#helper(node) }, |node| #type_name::convert(cx, node));
}
}
TypeKind::VecOfOption => {
quote! {
let #field_name = convert_vec_of_option(unsafe { hermes::parser::#helper(node) }, |node| #type_name::convert(cx, node));
}
}
}
})
.collect();
let type_ = format_ident!("{}", self.type_.as_ref().unwrap_or(&name_str.to_string()));
quote! {
impl FromHermes for #name {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let node_ref = node.as_ref();
assert_eq!(node_ref.kind, NodeKind::#type_);
let range = convert_range(cx, node);
#(#fields)*
Self {
#(#field_names,)*
loc: None,
range: Some(range),
}
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Field {
// TODO: deserialize with `parse_type` into a custom type
#[serde(rename = "type")]
pub type_: String,
#[serde(default)]
pub optional: bool,
#[serde(default)]
pub flatten: bool,
#[serde(default)]
pub rename: Option<String>,
#[serde(default)]
pub skip: bool,
#[serde(default)]
#[serde(rename = "TODO")]
pub todo: Option<String>,
#[serde(default)]
pub hermes_convert_with: Option<String>,
#[serde(default)]
pub hermes_default: bool,
}
impl Field {
pub fn codegen(&self, name: &str) -> TokenStream {
let name = format_ident!("{}", name);
parse_type(&self.type_).unwrap();
let type_name: Type = syn::parse_str(&self.type_)
.unwrap_or_else(|_| panic!("Expected a type name, got `{}`", &self.type_));
let type_ = quote!(#type_name);
let mut field = quote!(pub #name: #type_);
if self.optional {
field = quote! {
#[serde(default)]
#field
}
}
if self.flatten {
field = quote! {
#[serde(flatten)]
#field
}
}
if self.skip {
field = quote! {
#[serde(skip)]
#field
}
}
if let Some(rename) = &self.rename {
field = quote! {
#[serde(rename = #rename)]
#field
}
}
field
}
pub fn codegen_node(&self, name: &str) -> TokenStream {
let name = format_ident!("{}", name);
parse_type(&self.type_).unwrap();
let type_name: Type = syn::parse_str(&self.type_)
.unwrap_or_else(|_| panic!("Expected a type name, got `{}`", &self.type_));
let type_ = quote!(#type_name);
let mut field = quote!(pub #name: #type_);
if self.optional {
field = quote! {
#[serde(default)]
#field
}
}
if self.flatten {
field = quote! {
#[serde(flatten)]
#field
}
}
if self.skip {
field = quote! {
#[serde(skip)]
#field
}
}
if let Some(rename) = &self.rename {
field = quote! {
#[serde(rename = #rename)]
#field
}
}
field
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(transparent)]
#[serde(deny_unknown_fields)]
pub struct Enum {
pub variants: Vec<String>,
}
impl Enum {
pub fn codegen(&self, name: &str, enums: &IndexMap<String, Enum>) -> TokenStream {
let mut sorted_variants: Vec<_> = self.variants.iter().collect();
sorted_variants.sort();
let name_str = name;
let name = format_ident!("{}", name);
let variants: Vec<_> = sorted_variants
.iter()
.map(|name| {
let variant = format_ident!("{}", name);
if enums.contains_key(*name) {
quote!(#variant(#variant))
} else {
quote!(#variant(Box<#variant>))
}
})
.collect();
let enum_tag = format_ident!("__{}Tag", name);
let mut seen = HashSet::new();
// tag_variants is used to generate an enum of all the possible type tags (`type` values)
// that can appear in this enum. we emit this enum and derive a deserializer for it so that
// our enum deserializer can first decode the tag in order to know how to decode the data
let mut tag_variants = Vec::new();
// once the tag is decoded, we need to match against it and deserialize according the tag (`type`)
// tag_matches are the match arms for each type.
let mut tag_matches = Vec::new();
// Imagine a case like:
// enum ModuleItem {
// ImportDeclaration, // struct
// Statement // another enum
// }
// We need to generate matches for all the possible *concrete* `type` values, which means
// we have to expand nested enums such as `Statement`
for variant in self.variants.iter() {
if let Some(nested_enum) = enums.get(variant) {
let outer_variant = format_ident!("{}", variant);
for variant in nested_enum.variants.iter() {
// Skip variants that appear in multiple nested enums, we deserialize
// as the first listed outer variant
if !seen.insert(variant.to_string()) {
continue;
}
// Modeling ESTree only requires a single level of nested enums,
// so that's all we support. Though in theory we could support arbitrary nesting,
// since ultimately we're matching based on the final concrete types.
assert!(!enums.contains_key(variant));
let inner_variant = format_ident!("{}", variant);
tag_variants.push(quote!(#inner_variant));
tag_matches.push(quote! {
#enum_tag::#inner_variant => {
let node: Box<#inner_variant> = <Box<#inner_variant> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(#name::#outer_variant(#outer_variant::#inner_variant(node)))
}
});
}
} else {
if !seen.insert(variant.to_string()) {
panic!(
"Concrete variant {} was already added by a nested enum",
variant
);
}
let variant_name = format_ident!("{}", variant);
tag_variants.push(quote!(#variant_name));
tag_matches.push(quote! {
#enum_tag::#variant_name => {
let node: Box<#variant_name> = <Box<#variant_name> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(#name::#variant_name(node))
}
})
}
}
quote! {
#[derive(Serialize, Clone, Debug)]
#[serde(untagged)]
pub enum #name {
#(#variants),*
}
#[derive(Deserialize, Debug)]
enum #enum_tag {
#(#tag_variants),*
}
impl <'de> serde::Deserialize<'de> for #name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
let tagged = serde::Deserializer::deserialize_any(
deserializer,
serde::__private::de::TaggedContentVisitor::<#enum_tag>::new("type", #name_str)
)?;
match tagged.0 {
#(#tag_matches),*
}
}
}
}
}
pub fn codegen_visitor(&self, name: &str) -> TokenStream {
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(name));
let name = format_ident!("{}", name);
let mut tag_matches = Vec::new();
for variant in self.variants.iter() {
let node_variant = format_ident!("{}", variant);
let visitor_name = format_ident!("visit_{}", to_lower_snake_case(variant));
tag_matches.push(quote! {
#name::#node_variant(ast) => {
self.#visitor_name(ast);
}
})
}
quote! {
fn #visitor_name(&mut self, ast: &#name) {
match ast {
#(#tag_matches),*
}
}
}
}
pub fn codegen_hermes(&self, name: &str, grammar: &Grammar) -> TokenStream {
let name_str = name;
let name = format_ident!("{}", name);
let mut tag_matches = Vec::new();
let mut seen = HashSet::new();
// Imagine a case like:
// enum ModuleItem {
// ImportDeclaration, // struct
// Statement // another enum
// }
// We need to generate matches for all the possible *concrete* `type` values, which means
// we have to expand nested enums such as `Statement`
for variant in self.variants.iter() {
if let Some(nested_enum) = grammar.enums.get(variant) {
let outer_variant = format_ident!("{}", variant);
for variant in nested_enum.variants.iter() {
// Skip variants that appear in multiple nested enums, we deserialize
// as the first listed outer variant
if !seen.insert(variant.to_string()) {
continue;
}
// Modeling ESTree only requires a single level of nested enums,
// so that's all we support. Though in theory we could support arbitrary nesting,
// since ultimately we're matching based on the final concrete types.
assert!(!grammar.enums.contains_key(variant));
let node = grammar.nodes.get(variant).unwrap();
if node.skip_hermes_enum_variant {
continue;
}
let inner_variant = format_ident!("{}", variant);
let node_variant_name = node.type_.as_ref().unwrap_or(variant);
let node_variant = format_ident!("{}", node_variant_name);
tag_matches.push(quote! {
NodeKind::#node_variant => {
let node = #inner_variant::convert(cx, node);
#name::#outer_variant(#outer_variant::#inner_variant(Box::new(node)))
}
});
}
} else {
if !seen.insert(variant.to_string()) {
panic!(
"Concrete variant {} was already added by a nested enum",
variant
);
}
let variant_name = format_ident!("{}", variant);
let node = grammar.nodes.get(variant).unwrap();
if node.skip_hermes_enum_variant {
continue;
}
let node_variant_name = node.type_.as_ref().unwrap_or(variant);
let node_variant = format_ident!("{}", node_variant_name);
tag_matches.push(quote! {
NodeKind::#node_variant => {
let node = #variant_name::convert(cx, node);
#name::#variant_name(Box::new(node))
}
})
}
}
quote! {
impl FromHermes for #name {
fn convert(cx: &mut Context, node: NodePtr) -> Self {
let node_ref = node.as_ref();
match node_ref.kind {
#(#tag_matches),*
_ => panic!("Unexpected node kind `{:?}` for `{}`", node_ref.kind, #name_str)
}
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(transparent)]
#[serde(deny_unknown_fields)]
pub struct Operator {
pub variants: IndexMap<String, String>,
}
impl Operator {
pub fn codegen(&self, name: &str) -> TokenStream {
let mut sorted_variants: Vec<_> = self.variants.iter().collect();
sorted_variants.sort();
let name = format_ident!("{}", name);
let variants: Vec<_> = sorted_variants
.iter()
.map(|(name, operator)| {
let name = format_ident!("{}", name);
let comment = format!(" {}", &operator);
quote! {
#[doc = #comment]
#[serde(rename = #operator)]
#name
}
})
.collect();
let display_matches: Vec<_> = sorted_variants
.iter()
.map(|(name, operator)| {
let name = format_ident!("{}", name);
quote!(Self::#name => #operator)
})
.collect();
let fromstr_matches: Vec<_> = sorted_variants
.iter()
.map(|(name, operator)| {
let name = format_ident!("{}", name);
quote!(#operator => Ok(Self::#name))
})
.collect();
quote! {
#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum #name {
#(#variants),*
}
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
#(#display_matches),*
};
f.write_str(name)
}
}
impl std::str::FromStr for #name {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#(#fromstr_matches,)*
_ => Err(()),
}
}
}
}
}
pub fn codegen_hermes(&self, name: &str) -> TokenStream {
let mut sorted_variants: Vec<_> = self.variants.iter().collect();
sorted_variants.sort();
let name = format_ident!("{}", name);
quote! {
impl FromHermesLabel for #name {
fn convert(cx: &mut Context, label: NodeLabel) -> Self {
let utf_str = utf8_with_surrogates_to_string(label.as_slice()).unwrap();
utf_str.parse().unwrap()
}
}
}
}
}
enum TypeKind {
/// T
Named,
/// Option<T>
Option,
/// Vec<T>
Vec,
/// Vec<Option<T>>
VecOfOption,
}
/// Parses a given type into the underlying type name plus a descriptor of the
/// kind of type. Only a subset of Rust types are supported:
/// - T
/// - Option<T>
/// - Vec<T>
/// - Vec<Option<T>>
fn parse_type(type_: &str) -> Result<(String, TypeKind), String> {
let mut current = type_;
let mut is_list = false;
let mut is_option = false;
if current.starts_with("Vec<") {
current = &current[4..current.len() - 1];
is_list = true;
}
if current.starts_with("Option<") {
current = &current[7..current.len() - 1];
is_option = true;
}
if current.contains('<') {
Err(format!(
"Unsupported type `{current}` expected named type (`Identifier`), optional type (`Option<Identifier>`), list type (`Vec<Identifier>`), or optional list (`Vec<Option<Identifier>>`)"
))
} else {
let kind = match (is_list, is_option) {
(true, true) => TypeKind::VecOfOption,
(true, false) => TypeKind::Vec,
(false, true) => TypeKind::Option,
(false, false) => TypeKind::Named,
};
Ok((current.to_string(), kind))
}
}
// from https://github.com/rust-lang/rust-analyzer/blob/4105378dc7479a3dbd39a4afb3eba67d083bd7f8/xtask/src/codegen/gen_syntax.rs#L406C1-L418C2
fn to_lower_snake_case(s: &str) -> String {
let mut buf = String::with_capacity(s.len());
let mut prev = false;
for c in s.chars() {
if c.is_ascii_uppercase() {
if prev {
buf.push('_')
}
prev = false;
} else {
prev = true;
}
buf.push(c.to_ascii_lowercase());
}
buf
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
/**
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from './src/client/ReactFlightDOMClientEdge';
mod codegen;
pub use codegen::{estree, estree_hermes};

View File

@@ -0,0 +1,24 @@
[package]
name = "react_fixtures"
version = "0.1.0"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
insta = { workspace = true }
react_estree = { workspace = true }
react_hermes_parser = { workspace = true }
react_hir = { workspace = true }
react_optimization = { workspace = true }
react_semantic_analysis = { workspace = true }
react_ssa = { workspace = true }
react_build_hir = { workspace = true }
miette = { workspace = true, features = ["backtrace", "fancy"] }

View File

@@ -0,0 +1,3 @@
# fixtures
This crate is for tests only, and runs the suite of compiler fixture tests.

View File

@@ -1,7 +1,6 @@
/**
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export {transformFire} from './TransformFire';

View File

@@ -0,0 +1,34 @@
function Component() {
let a = 1;
let b;
if (a === 1) {
b = true;
} else {
b = false;
}
let c;
if (b) {
c = "hello";
} else {
c = null;
}
let d;
if (c === "hello") {
d = 42.0;
} else {
d = 42.001;
}
let e;
if (d === 42.0) {
e = "ok";
} else {
e = "nope";
}
// should constant-propagate to "ok"
return e;
}

View File

@@ -0,0 +1,60 @@
function Component(props) {
// global propagation
let a;
a = Math;
a; // Math
// primitive propagation w phi
let b;
if (props) {
b = true;
} else {
b = true;
}
b; // true
// primitive propagation fails if different values
let c;
if (props) {
c = true;
} else {
c = 42;
}
c; // <no change>
// constant evaluation
42 + 1; // 43
42 - 1; // 41
42 * 2; // 84
42 / 2; // 21
0 == 1; // false
0 != 1; // true
0 === 1; // false
0 !== 1; // true
0 == 0; // true
// TODO: unary operators
// 0 == -0; // false
// 0 != -0; // true
// 0 === -0; // false
// 0 !== -0; // true
NaN == NaN; // false
NaN != NaN; // true
NaN !== NaN; // true
NaN !== NaN; // true
"hello" == "hello"; // true
"hello" != "hello"; // false
"hello" === "hello"; // true
"hello" !== "hello"; // false
"hello" == "world"; // false
"hello" != "world"; // true
"hello" === "world"; // false
"hello" !== "world"; // true
true == true; // true
true != true; // false
true === true; // true
true !== true; // false
// constant evaluation through variable
let x = 5 * 60 * 60 * 1000; // 5 hours in milliseconds
x;
}

View File

@@ -0,0 +1,5 @@
function Component(a, b) {
const [c, , ...d] = a;
const [[[e]], ...[f]] = b;
return [c, d, e, f];
}

View File

@@ -0,0 +1,15 @@
function Component(a, b) {
const {
c,
d,
e: { e },
f: { _f: f },
g: {
g: {
g: { g, ...h },
},
},
...i
} = a;
return [c, d, e, f, g, h, i];
}

View File

@@ -0,0 +1,3 @@
function foo() {
x = true;
}

View File

@@ -0,0 +1,7 @@
function foo() {
let x = 0;
for (let i = 0; i < 10; i = i + 1) {
x = x + i;
}
return x;
}

View File

@@ -0,0 +1,21 @@
function Component(props) {
const x = 2;
const foo = function foo(y) {
let a = 1;
let b;
if (a === 1) {
b = 5 + 3;
} else {
b = false;
}
x + y + a + b;
const bar = function bar(z) {
let c = 2;
let d;
d = 3;
x + y + a + b + z + c + d;
};
bar;
foo;
};
}

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