Compare commits
1 Commits
asserts-st
...
pr35146
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad9f4678da |
@@ -331,7 +331,6 @@ module.exports = {
|
||||
'packages/react-server-dom-turbopack/**/*.js',
|
||||
'packages/react-server-dom-parcel/**/*.js',
|
||||
'packages/react-server-dom-fb/**/*.js',
|
||||
'packages/react-server-dom-unbundled/**/*.js',
|
||||
'packages/react-test-renderer/**/*.js',
|
||||
'packages/react-debug-tools/**/*.js',
|
||||
'packages/react-devtools-extensions/**/*.js',
|
||||
@@ -635,7 +634,6 @@ module.exports = {
|
||||
FocusOptions: 'readonly',
|
||||
OptionalEffectTiming: 'readonly',
|
||||
|
||||
__REACT_ROOT_PATH_TEST__: 'readonly',
|
||||
spyOnDev: 'readonly',
|
||||
spyOnDevAndProd: 'readonly',
|
||||
spyOnProd: 'readonly',
|
||||
|
||||
138
.github/workflows/runtime_build_and_test.yml
vendored
138
.github/workflows/runtime_build_and_test.yml
vendored
@@ -3,10 +3,6 @@ name: (Runtime) Build and Test
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
# To get CI for backport releases.
|
||||
# This will duplicate CI for releases from main which is acceptable
|
||||
- "v*"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- compiler/**
|
||||
@@ -45,7 +41,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
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'
|
||||
@@ -59,8 +55,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Save cache
|
||||
@@ -69,7 +67,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
runtime_compiler_node_modules_cache:
|
||||
name: Cache Runtime, Compiler node_modules
|
||||
@@ -84,7 +82,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
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'
|
||||
@@ -100,8 +98,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn --cwd compiler install --frozen-lockfile
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
|
||||
# ----- FLOW -----
|
||||
discover_flow_inline_configs:
|
||||
@@ -154,8 +154,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -182,8 +184,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -212,7 +216,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -270,15 +274,16 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: yarn --cwd compiler install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- run: node --version
|
||||
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
|
||||
|
||||
# Hardcoded to improve parallelism
|
||||
@@ -301,7 +306,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
- name: Install runtime dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
@@ -344,8 +349,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -383,6 +390,9 @@ jobs:
|
||||
-r=experimental --env=development,
|
||||
-r=experimental --env=production,
|
||||
|
||||
# Dev Tools
|
||||
--project=devtools -r=experimental,
|
||||
|
||||
# TODO: Update test config to support www build tests
|
||||
# - "-r=www-classic --env=development --variant=false"
|
||||
# - "-r=www-classic --env=production --variant=false"
|
||||
@@ -430,8 +440,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-and-compiler-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -446,54 +458,8 @@ jobs:
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: node --version
|
||||
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci
|
||||
|
||||
test_build_devtools:
|
||||
name: yarn test-build (devtools)
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard:
|
||||
- 1/5
|
||||
- 2/5
|
||||
- 3/5
|
||||
- 4/5
|
||||
- 5/5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha != '' && github.event.inputs.commit_sha || 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
|
||||
id: node_modules
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
if: steps.node_modules.outputs.cache-hit != 'true'
|
||||
- name: Restore archived build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: _build_*
|
||||
path: build
|
||||
merge-multiple: true
|
||||
- name: Display structure of build
|
||||
run: ls -R build
|
||||
- run: node --version
|
||||
- run: yarn test --build --project=devtools -r=experimental --shard=${{ matrix.shard }} --ci
|
||||
|
||||
process_artifacts_combined:
|
||||
name: Process artifacts combined
|
||||
needs: [build_and_lint, runtime_node_modules_cache]
|
||||
@@ -517,8 +483,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -580,8 +548,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -618,8 +588,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -768,8 +740,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
@@ -828,8 +802,10 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
|
||||
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
|
||||
runtime-node_modules-v6-
|
||||
- name: Ensure clean build directory
|
||||
run: rm -rf build
|
||||
- run: yarn install --frozen-lockfile
|
||||
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,9 +1,3 @@
|
||||
## 19.2.1 (Dec 3, 2025)
|
||||
|
||||
### React Server Components
|
||||
|
||||
- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277))
|
||||
|
||||
## 19.2.0 (October 1st, 2025)
|
||||
|
||||
Below is a list of all new features, APIs, and bug fixes.
|
||||
@@ -77,12 +71,6 @@ Read the [React 19.2 release post](https://react.dev/blog/2025/10/01/react-19-2)
|
||||
|
||||
- [createContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L255-L261) and [createHydrationContainer](https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L305-L312) had their parameter order adjusted after `on*` handlers to account for upcoming experimental APIs
|
||||
|
||||
## 19.1.2 (Dec 3, 2025)
|
||||
|
||||
### React Server Components
|
||||
|
||||
- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277))
|
||||
|
||||
## 19.1.1 (July 28, 2025)
|
||||
|
||||
### React
|
||||
@@ -135,12 +123,6 @@ An Owner Stack is a string representing the components that are directly respons
|
||||
* Exposed `registerServerReference` in client builds to handle server references in different environments. [#32534](https://github.com/facebook/react/pull/32534)
|
||||
* Added react-server-dom-parcel package which integrates Server Components with the [Parcel bundler](https://parceljs.org/) [#31725](https://github.com/facebook/react/pull/31725), [#32132](https://github.com/facebook/react/pull/32132), [#31799](https://github.com/facebook/react/pull/31799), [#32294](https://github.com/facebook/react/pull/32294), [#31741](https://github.com/facebook/react/pull/31741)
|
||||
|
||||
## 19.0.1 (Dec 3, 2025)
|
||||
|
||||
### React Server Components
|
||||
|
||||
- Bring React Server Component fixes to Server Actions (@sebmarkbage [#35277](https://github.com/facebook/react/pull/35277))
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(node scripts/enable-feature-flag.js:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
1
compiler/apps/playground/.gitignore
vendored
1
compiler/apps/playground/.gitignore
vendored
@@ -12,7 +12,6 @@
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
/next-env.d.ts
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
@@ -14,6 +14,7 @@ import React, {
|
||||
unstable_ViewTransition as ViewTransition,
|
||||
unstable_addTransitionType as addTransitionType,
|
||||
startTransition,
|
||||
Activity,
|
||||
} from 'react';
|
||||
import {Resizable} from 're-resizable';
|
||||
import {useStore, useStoreDispatch} from '../StoreContext';
|
||||
@@ -33,14 +34,9 @@ export default function ConfigEditor({
|
||||
}): React.ReactElement {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
// TODO: Add back <Activity> after upgrading next.js
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: isExpanded ? 'block' : 'none',
|
||||
}}>
|
||||
{/* <Activity mode={isExpanded ? 'visible' : 'hidden'}> */}
|
||||
<Activity mode={isExpanded ? 'visible' : 'hidden'}>
|
||||
<ExpandedEditor
|
||||
onToggle={() => {
|
||||
startTransition(() => {
|
||||
@@ -50,13 +46,8 @@ export default function ConfigEditor({
|
||||
}}
|
||||
formattedAppliedConfig={formattedAppliedConfig}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: !isExpanded ? 'block' : 'none',
|
||||
}}>
|
||||
{/* </Activity>
|
||||
<Activity mode={isExpanded ? 'hidden' : 'visible'}></Activity> */}
|
||||
</Activity>
|
||||
<Activity mode={isExpanded ? 'hidden' : 'visible'}>
|
||||
<CollapsedEditor
|
||||
onToggle={() => {
|
||||
startTransition(() => {
|
||||
@@ -65,8 +56,7 @@ export default function ConfigEditor({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* </Activity> */}
|
||||
</Activity>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -126,9 +116,8 @@ function ExpandedEditor({
|
||||
|
||||
return (
|
||||
<ViewTransition
|
||||
update={{[CONFIG_PANEL_TRANSITION]: 'slide-in', default: 'none'}}>
|
||||
{/* enter={{[CONFIG_PANEL_TRANSITION]: 'slide-in', default: 'none'}}
|
||||
exit={{[CONFIG_PANEL_TRANSITION]: 'slide-out', default: 'none'}}> */}
|
||||
enter={{[CONFIG_PANEL_TRANSITION]: 'slide-in', default: 'none'}}
|
||||
exit={{[CONFIG_PANEL_TRANSITION]: 'slide-out', default: 'none'}}>
|
||||
<Resizable
|
||||
minWidth={300}
|
||||
maxWidth={600}
|
||||
|
||||
6
compiler/apps/playground/next-env.d.ts
vendored
Normal file
6
compiler/apps/playground/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -35,13 +35,13 @@
|
||||
"lru-cache": "^11.2.2",
|
||||
"lz-string": "^1.5.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"next": "15.5.9",
|
||||
"next": "15.6.0-canary.7",
|
||||
"notistack": "^3.0.0-alpha.7",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-format": "^29.3.1",
|
||||
"re-resizable": "^6.9.16",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
"react": "19.2",
|
||||
"react-dom": "19.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.9",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
@@ -715,10 +715,10 @@
|
||||
dependencies:
|
||||
"@monaco-editor/loader" "^1.6.1"
|
||||
|
||||
"@next/env@15.5.9":
|
||||
version "15.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.5.9.tgz#53c2c34dc17cd87b61f70c6cc211e303123b2ab8"
|
||||
integrity sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==
|
||||
"@next/env@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.6.0-canary.7.tgz#cdbf2967a9437ef09eef755e203f315acc4d8d8f"
|
||||
integrity sha512-LNZ7Yd3Cl9rKvjYdeJmszf2HmSDP76SQmfafKep2Ux16ZXKoN5OjwVHFTltKNdsB3vt2t+XJzLP2rhw5lBoFBA==
|
||||
|
||||
"@next/eslint-plugin-next@15.5.2":
|
||||
version "15.5.2"
|
||||
@@ -727,45 +727,45 @@
|
||||
dependencies:
|
||||
fast-glob "3.3.1"
|
||||
|
||||
"@next/swc-darwin-arm64@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz#f0c9ccfec2cd87cbd4b241ce4c779a7017aed958"
|
||||
integrity sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==
|
||||
"@next/swc-darwin-arm64@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.6.0-canary.7.tgz#628cd34ce9120000f1cb5b08963426431174fc57"
|
||||
integrity sha512-POsBrxhrR3qvqXV+JZ6ZoBc8gJf8rhYe+OedceI1piPVqtJYOJa3EB4eaqcc+kMsllKRrH/goNlhLwtyhE+0Qg==
|
||||
|
||||
"@next/swc-darwin-x64@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz#18009e9fcffc5c0687cc9db24182ddeac56280d9"
|
||||
integrity sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==
|
||||
"@next/swc-darwin-x64@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.6.0-canary.7.tgz#37d4ebab14da74a2f8028daf6d76aab410153e06"
|
||||
integrity sha512-lmk9ysBuSiPlAJZTCo/3O4mXNFosg6EDIf4GrmynIwCG2as6/KxzyD1WqFp56Exp8eFDjP7SFapD10sV43vCsA==
|
||||
|
||||
"@next/swc-linux-arm64-gnu@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz#fe7c7e08264cf522d4e524299f6d3e63d68d579a"
|
||||
integrity sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==
|
||||
"@next/swc-linux-arm64-gnu@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.6.0-canary.7.tgz#ce700cc0e0d24763136838223105a524b36694fa"
|
||||
integrity sha512-why8k6d0SBm3AKoOD5S7ir3g+BF34l9oFKIoZrLaZaKBvNGpFcjc7Ovc2TunNMeaMJzv9k1dHYSap0EI5oSuzg==
|
||||
|
||||
"@next/swc-linux-arm64-musl@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz#94228fe293475ec34a5a54284e1056876f43a3cf"
|
||||
integrity sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==
|
||||
"@next/swc-linux-arm64-musl@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.6.0-canary.7.tgz#c791b8e15bf2c338b4cc0387fe7afb3ef83ecfcf"
|
||||
integrity sha512-HzvTRsKvYj32Va4YuJN3n3xOxvk+6QwB63d/EsgmdkeA/vrqciUAmJDYpuzZEvRc3Yp2nyPq8KZxtHAr6ISZ2Q==
|
||||
|
||||
"@next/swc-linux-x64-gnu@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz#078c71201dfe7fcfb8fa6dc92aae6c94bc011cdc"
|
||||
integrity sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==
|
||||
"@next/swc-linux-x64-gnu@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.6.0-canary.7.tgz#c01c3a3d8e71660c49298dd053d078379b6b5919"
|
||||
integrity sha512-6yRFrg2qWXOqa+1BI53J9EmHWFzKg9U2r+5R7n7BFUp8PH5SC92WBsmYTnh/RkvAYvdupiVzMervwwswCs6kFg==
|
||||
|
||||
"@next/swc-linux-x64-musl@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz#72947f5357f9226292353e0bb775643da3c7a182"
|
||||
integrity sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==
|
||||
"@next/swc-linux-x64-musl@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.6.0-canary.7.tgz#3f4b39faef4a5f88b13e4c726b008ddc9717f819"
|
||||
integrity sha512-O/JjvOvNK/Wao/OIQaA6evDkxkmFFQgJ1/hI1dVk6/PAeKmW2/Q+6Dodh97eAkOwedS1ZdQl2mojf87TzLvzdQ==
|
||||
|
||||
"@next/swc-win32-arm64-msvc@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz#397b912cd51c6a80e32b9c0507ecd82514353941"
|
||||
integrity sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==
|
||||
"@next/swc-win32-arm64-msvc@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.6.0-canary.7.tgz#9bc5da0907b7ce67eedda02a6d56a09d9a539ccf"
|
||||
integrity sha512-p9DvrDgnePofZCtiWVY7qZtwXxiOGJlAyy2LoGPYSGOUDhjbTG8j6XMUFXpV9UwpH+l7st522psO1BVzbpT8IQ==
|
||||
|
||||
"@next/swc-win32-x64-msvc@15.5.7":
|
||||
version "15.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz#e02b543d9dc6c1631d4ac239cb1177245dfedfe4"
|
||||
integrity sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==
|
||||
"@next/swc-win32-x64-msvc@15.6.0-canary.7":
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.6.0-canary.7.tgz#5b271c591ccbe67d5fa966dd22db86c547414fd1"
|
||||
integrity sha512-f1ywT3xWu4StWKA1mZRyGfelu/h+W0OEEyBxQNXzXyYa0VGZb9LyCNb5cYoNKBm0Bw18Hp1PVe0bHuusemGCcw==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
@@ -3204,25 +3204,25 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
next@15.5.9:
|
||||
version "15.5.9"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-15.5.9.tgz#1b80d05865cc27e710fb4dcfc6fd9e726ed12ad4"
|
||||
integrity sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==
|
||||
next@15.6.0-canary.7:
|
||||
version "15.6.0-canary.7"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-15.6.0-canary.7.tgz#bfc2ac3c9a78e23d550c303d18247a263e6b5bc1"
|
||||
integrity sha512-4ukX2mxat9wWT6E0Gw/3TOR9ULV1q399E42F86cwsPSFgTWa04ABhcTqO0r9J/QR1YWPR8WEgh9qUzmWA/1yEw==
|
||||
dependencies:
|
||||
"@next/env" "15.5.9"
|
||||
"@next/env" "15.6.0-canary.7"
|
||||
"@swc/helpers" "0.5.15"
|
||||
caniuse-lite "^1.0.30001579"
|
||||
postcss "8.4.31"
|
||||
styled-jsx "5.1.6"
|
||||
optionalDependencies:
|
||||
"@next/swc-darwin-arm64" "15.5.7"
|
||||
"@next/swc-darwin-x64" "15.5.7"
|
||||
"@next/swc-linux-arm64-gnu" "15.5.7"
|
||||
"@next/swc-linux-arm64-musl" "15.5.7"
|
||||
"@next/swc-linux-x64-gnu" "15.5.7"
|
||||
"@next/swc-linux-x64-musl" "15.5.7"
|
||||
"@next/swc-win32-arm64-msvc" "15.5.7"
|
||||
"@next/swc-win32-x64-msvc" "15.5.7"
|
||||
"@next/swc-darwin-arm64" "15.6.0-canary.7"
|
||||
"@next/swc-darwin-x64" "15.6.0-canary.7"
|
||||
"@next/swc-linux-arm64-gnu" "15.6.0-canary.7"
|
||||
"@next/swc-linux-arm64-musl" "15.6.0-canary.7"
|
||||
"@next/swc-linux-x64-gnu" "15.6.0-canary.7"
|
||||
"@next/swc-linux-x64-musl" "15.6.0-canary.7"
|
||||
"@next/swc-win32-arm64-msvc" "15.6.0-canary.7"
|
||||
"@next/swc-win32-x64-msvc" "15.6.0-canary.7"
|
||||
sharp "^0.34.3"
|
||||
|
||||
node-releases@^2.0.18:
|
||||
@@ -3582,10 +3582,10 @@ 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.2.3:
|
||||
version "19.2.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.3.tgz#f0b61d7e5c4a86773889fcc1853af3ed5f215b17"
|
||||
integrity sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==
|
||||
react-dom@19.2:
|
||||
version "19.2.0"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz#00ed1e959c365e9a9d48f8918377465466ec3af8"
|
||||
integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==
|
||||
dependencies:
|
||||
scheduler "^0.27.0"
|
||||
|
||||
@@ -3599,10 +3599,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.2.3:
|
||||
version "19.2.3"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-19.2.3.tgz#d83e5e8e7a258cf6b4fe28640515f99b87cd19b8"
|
||||
integrity sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==
|
||||
react@19.2:
|
||||
version "19.2.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-19.2.0.tgz#d33dd1721698f4376ae57a54098cb47fc75d93a5"
|
||||
integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==
|
||||
|
||||
read-cache@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
||||
@@ -304,30 +304,6 @@ export class CompilerError extends Error {
|
||||
disabledDetails: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
|
||||
printedMessage: string | null = null;
|
||||
|
||||
static simpleInvariant(
|
||||
condition: unknown,
|
||||
options: {
|
||||
reason: CompilerDiagnosticOptions['reason'];
|
||||
description?: CompilerDiagnosticOptions['description'];
|
||||
loc: SourceLocation;
|
||||
},
|
||||
): asserts condition {
|
||||
if (!condition) {
|
||||
const errors = new CompilerError();
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
reason: options.reason,
|
||||
description: options.description ?? null,
|
||||
category: ErrorCategory.Invariant,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: options.loc,
|
||||
message: options.reason,
|
||||
}),
|
||||
);
|
||||
throw errors;
|
||||
}
|
||||
}
|
||||
static invariant(
|
||||
condition: unknown,
|
||||
options: Omit<CompilerDiagnosticOptions, 'category'>,
|
||||
@@ -600,9 +576,7 @@ function printErrorSummary(category: ErrorCategory, message: string): string {
|
||||
case ErrorCategory.Suppression:
|
||||
case ErrorCategory.Syntax:
|
||||
case ErrorCategory.UseMemo:
|
||||
case ErrorCategory.VoidUseMemo:
|
||||
case ErrorCategory.MemoDependencies:
|
||||
case ErrorCategory.EffectExhaustiveDependencies: {
|
||||
case ErrorCategory.VoidUseMemo: {
|
||||
heading = 'Error';
|
||||
break;
|
||||
}
|
||||
@@ -660,10 +634,6 @@ export enum ErrorCategory {
|
||||
* Checks that manual memoization is preserved
|
||||
*/
|
||||
PreserveManualMemo = 'PreserveManualMemo',
|
||||
/**
|
||||
* Checks for exhaustive useMemo/useCallback dependencies without extraneous values
|
||||
*/
|
||||
MemoDependencies = 'MemoDependencies',
|
||||
/**
|
||||
* Checks for known incompatible libraries
|
||||
*/
|
||||
@@ -684,10 +654,6 @@ export enum ErrorCategory {
|
||||
* Checks for memoized effect deps
|
||||
*/
|
||||
EffectDependencies = 'EffectDependencies',
|
||||
/**
|
||||
* Checks for exhaustive and extraneous effect dependencies
|
||||
*/
|
||||
EffectExhaustiveDependencies = 'EffectExhaustiveDependencies',
|
||||
/**
|
||||
* Checks for no setState in effect bodies
|
||||
*/
|
||||
@@ -843,16 +809,6 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.EffectExhaustiveDependencies: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'exhaustive-effect-dependencies',
|
||||
description:
|
||||
'Validates that effect dependencies are exhaustive and without extraneous values',
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.EffectDerivationsOfState: {
|
||||
return {
|
||||
category,
|
||||
@@ -869,9 +825,7 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'set-state-in-effect',
|
||||
description:
|
||||
'Validates against calling setState synchronously in an effect. ' +
|
||||
'This can indicate non-local derived data, a derived event pattern, or ' +
|
||||
'improper external data synchronization.',
|
||||
'Validates against calling setState synchronously in an effect, which can lead to re-renders that degrade performance',
|
||||
preset: LintRulePreset.Recommended,
|
||||
};
|
||||
}
|
||||
@@ -1077,24 +1031,6 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
|
||||
preset: LintRulePreset.RecommendedLatest,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.MemoDependencies: {
|
||||
return {
|
||||
category,
|
||||
severity: ErrorSeverity.Error,
|
||||
name: 'memo-dependencies',
|
||||
description:
|
||||
'Validates that useMemo() and useCallback() specify comprehensive dependencies without extraneous values. See [`useMemo()` docs](https://react.dev/reference/react/useMemo) for more information.',
|
||||
/**
|
||||
* TODO: the "MemoDependencies" rule largely reimplements the "exhaustive-deps" non-compiler rule,
|
||||
* allowing the compiler to ensure it does not regress change behavior due to different dependencies.
|
||||
* We previously relied on the source having ESLint suppressions for any exhaustive-deps violations,
|
||||
* but it's more reliable to verify it within the compiler.
|
||||
*
|
||||
* Long-term we should de-duplicate these implementations.
|
||||
*/
|
||||
preset: LintRulePreset.Off,
|
||||
};
|
||||
}
|
||||
case ErrorCategory.IncompatibleLibrary: {
|
||||
return {
|
||||
category,
|
||||
|
||||
@@ -102,25 +102,14 @@ export type PluginOptions = Partial<{
|
||||
|
||||
panicThreshold: PanicThresholdOptions;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
/*
|
||||
* When enabled, Forget will continue statically analyzing and linting code, but skip over codegen
|
||||
* passes.
|
||||
*
|
||||
* NOTE: ignored if `outputMode` is specified
|
||||
*
|
||||
* Defaults to false
|
||||
*/
|
||||
noEmit: boolean;
|
||||
|
||||
/**
|
||||
* If specified, overrides `noEmit` and controls the output mode of the compiler.
|
||||
*
|
||||
* Defaults to null
|
||||
*/
|
||||
outputMode: CompilerOutputMode | null;
|
||||
|
||||
/*
|
||||
* Determines the strategy for determining which functions to compile. Note that regardless of
|
||||
* which mode is enabled, a component can be opted out by adding the string literal
|
||||
@@ -223,19 +212,6 @@ const CompilationModeSchema = z.enum([
|
||||
|
||||
export type CompilationMode = z.infer<typeof CompilationModeSchema>;
|
||||
|
||||
const CompilerOutputModeSchema = z.enum([
|
||||
// Build optimized for SSR, with client features removed
|
||||
'ssr',
|
||||
// Build optimized for the client, with auto memoization
|
||||
'client',
|
||||
// Build optimized for the client without auto memo
|
||||
'client-no-memo',
|
||||
// Lint mode, the output is unused but validations should run
|
||||
'lint',
|
||||
]);
|
||||
|
||||
export type CompilerOutputMode = z.infer<typeof CompilerOutputModeSchema>;
|
||||
|
||||
/**
|
||||
* Represents 'events' that may occur during compilation. Events are only
|
||||
* recorded when a logger is set (through the config).
|
||||
@@ -317,7 +293,6 @@ export const defaultOptions: ParsedPluginOptions = {
|
||||
logger: null,
|
||||
gating: null,
|
||||
noEmit: false,
|
||||
outputMode: null,
|
||||
dynamicGating: null,
|
||||
eslintSuppressionRules: null,
|
||||
flowSuppressions: true,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import {NodePath} from '@babel/traverse';
|
||||
import * as t from '@babel/types';
|
||||
import prettyFormat from 'pretty-format';
|
||||
import {CompilerOutputMode, Logger, ProgramContext} from '.';
|
||||
import {Logger, ProgramContext} from '.';
|
||||
import {
|
||||
HIRFunction,
|
||||
ReactiveFunction,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
pruneUnusedLabelsHIR,
|
||||
} from '../HIR';
|
||||
import {
|
||||
CompilerMode,
|
||||
Environment,
|
||||
EnvironmentConfig,
|
||||
ReactFunctionType,
|
||||
@@ -104,8 +105,6 @@ import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRan
|
||||
import {validateNoDerivedComputationsInEffects} from '../Validation/ValidateNoDerivedComputationsInEffects';
|
||||
import {validateNoDerivedComputationsInEffects_exp} from '../Validation/ValidateNoDerivedComputationsInEffects_exp';
|
||||
import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
|
||||
import {optimizeForSSR} from '../Optimization/OptimizeForSSR';
|
||||
import {validateExhaustiveDependencies} from '../Validation/ValidateExhaustiveDependencies';
|
||||
import {validateSourceLocations} from '../Validation/ValidateSourceLocations';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
@@ -120,7 +119,7 @@ function run(
|
||||
>,
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
mode: CompilerOutputMode,
|
||||
mode: CompilerMode,
|
||||
programContext: ProgramContext,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
@@ -170,7 +169,7 @@ function runWithEnvironment(
|
||||
validateUseMemo(hir).unwrap();
|
||||
|
||||
if (
|
||||
env.enableDropManualMemoization &&
|
||||
env.isInferredMemoEnabled &&
|
||||
!env.config.enablePreserveExistingManualUseMemo &&
|
||||
!env.config.disableMemoizationForDebugging &&
|
||||
!env.config.enableChangeDetectionForDebugging
|
||||
@@ -206,7 +205,7 @@ function runWithEnvironment(
|
||||
inferTypes(hir);
|
||||
log({kind: 'hir', name: 'InferTypes', value: hir});
|
||||
|
||||
if (env.enableValidations) {
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (env.config.validateHooksUsage) {
|
||||
validateHooksUsage(hir).unwrap();
|
||||
}
|
||||
@@ -232,17 +231,12 @@ function runWithEnvironment(
|
||||
|
||||
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
|
||||
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
|
||||
if (env.enableValidations) {
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (mutabilityAliasingErrors.isErr()) {
|
||||
throw mutabilityAliasingErrors.unwrapErr();
|
||||
}
|
||||
}
|
||||
|
||||
if (env.outputMode === 'ssr') {
|
||||
optimizeForSSR(hir);
|
||||
log({kind: 'hir', name: 'OptimizeForSSR', value: hir});
|
||||
}
|
||||
|
||||
// Note: Has to come after infer reference effects because "dead" code may still affect inference
|
||||
deadCodeElimination(hir);
|
||||
log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
|
||||
@@ -259,14 +253,14 @@ function runWithEnvironment(
|
||||
isFunctionExpression: false,
|
||||
});
|
||||
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
|
||||
if (env.enableValidations) {
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (mutabilityAliasingRangeErrors.isErr()) {
|
||||
throw mutabilityAliasingRangeErrors.unwrapErr();
|
||||
}
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
}
|
||||
|
||||
if (env.enableValidations) {
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (env.config.assertValidMutableRanges) {
|
||||
assertValidMutableRanges(hir);
|
||||
}
|
||||
@@ -279,20 +273,17 @@ function runWithEnvironment(
|
||||
validateNoSetStateInRender(hir).unwrap();
|
||||
}
|
||||
|
||||
if (
|
||||
env.config.validateNoDerivedComputationsInEffects_exp &&
|
||||
env.outputMode === 'lint'
|
||||
) {
|
||||
if (env.config.validateNoDerivedComputationsInEffects_exp) {
|
||||
env.logErrors(validateNoDerivedComputationsInEffects_exp(hir));
|
||||
} else if (env.config.validateNoDerivedComputationsInEffects) {
|
||||
validateNoDerivedComputationsInEffects(hir);
|
||||
}
|
||||
|
||||
if (env.config.validateNoSetStateInEffects && env.outputMode === 'lint') {
|
||||
if (env.config.validateNoSetStateInEffects) {
|
||||
env.logErrors(validateNoSetStateInEffects(hir, env));
|
||||
}
|
||||
|
||||
if (env.config.validateNoJSXInTryStatements && env.outputMode === 'lint') {
|
||||
if (env.config.validateNoJSXInTryStatements) {
|
||||
env.logErrors(validateNoJSXInTryStatement(hir));
|
||||
}
|
||||
|
||||
@@ -306,16 +297,6 @@ function runWithEnvironment(
|
||||
inferReactivePlaces(hir);
|
||||
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
|
||||
|
||||
if (env.enableValidations) {
|
||||
if (
|
||||
env.config.validateExhaustiveMemoizationDependencies ||
|
||||
env.config.validateExhaustiveEffectDependencies
|
||||
) {
|
||||
// NOTE: this relies on reactivity inference running first
|
||||
validateExhaustiveDependencies(hir).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
rewriteInstructionKindsBasedOnReassignment(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
@@ -323,15 +304,11 @@ function runWithEnvironment(
|
||||
value: hir,
|
||||
});
|
||||
|
||||
if (
|
||||
env.enableValidations &&
|
||||
env.config.validateStaticComponents &&
|
||||
env.outputMode === 'lint'
|
||||
) {
|
||||
env.logErrors(validateStaticComponents(hir));
|
||||
}
|
||||
if (env.isInferredMemoEnabled) {
|
||||
if (env.config.validateStaticComponents) {
|
||||
env.logErrors(validateStaticComponents(hir));
|
||||
}
|
||||
|
||||
if (env.enableMemoization) {
|
||||
/**
|
||||
* Only create reactive scopes (which directly map to generated memo blocks)
|
||||
* if inferred memoization is enabled. This makes all later passes which
|
||||
@@ -603,7 +580,7 @@ export function compileFn(
|
||||
>,
|
||||
config: EnvironmentConfig,
|
||||
fnType: ReactFunctionType,
|
||||
mode: CompilerOutputMode,
|
||||
mode: CompilerMode,
|
||||
programContext: ProgramContext,
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
validateRestrictedImports,
|
||||
} from './Imports';
|
||||
import {
|
||||
CompilerOutputMode,
|
||||
CompilerReactTarget,
|
||||
ParsedPluginOptions,
|
||||
PluginOptions,
|
||||
@@ -400,15 +399,7 @@ export function compileProgram(
|
||||
*/
|
||||
const suppressions = findProgramSuppressions(
|
||||
pass.comments,
|
||||
/*
|
||||
* If the compiler is validating hooks rules and exhaustive memo dependencies, we don't need to check
|
||||
* for React ESLint suppressions
|
||||
*/
|
||||
pass.opts.environment.validateExhaustiveMemoizationDependencies &&
|
||||
pass.opts.environment.validateHooksUsage
|
||||
? null
|
||||
: (pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS),
|
||||
// Always bail on Flow suppressions
|
||||
pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS,
|
||||
pass.opts.flowSuppressions,
|
||||
);
|
||||
|
||||
@@ -430,17 +421,9 @@ export function compileProgram(
|
||||
);
|
||||
const compiledFns: Array<CompileResult> = [];
|
||||
|
||||
// outputMode takes precedence if specified
|
||||
const outputMode: CompilerOutputMode =
|
||||
pass.opts.outputMode ?? (pass.opts.noEmit ? 'lint' : 'client');
|
||||
while (queue.length !== 0) {
|
||||
const current = queue.shift()!;
|
||||
const compiled = processFn(
|
||||
current.fn,
|
||||
current.fnType,
|
||||
programContext,
|
||||
outputMode,
|
||||
);
|
||||
const compiled = processFn(current.fn, current.fnType, programContext);
|
||||
|
||||
if (compiled != null) {
|
||||
for (const outlined of compiled.outlined) {
|
||||
@@ -598,7 +581,6 @@ function processFn(
|
||||
fn: BabelFn,
|
||||
fnType: ReactFunctionType,
|
||||
programContext: ProgramContext,
|
||||
outputMode: CompilerOutputMode,
|
||||
): null | CodegenFunction {
|
||||
let directives: {
|
||||
optIn: t.Directive | null;
|
||||
@@ -634,27 +616,18 @@ function processFn(
|
||||
}
|
||||
|
||||
let compiledFn: CodegenFunction;
|
||||
const compileResult = tryCompileFunction(
|
||||
fn,
|
||||
fnType,
|
||||
programContext,
|
||||
outputMode,
|
||||
);
|
||||
const compileResult = tryCompileFunction(fn, fnType, programContext);
|
||||
if (compileResult.kind === 'error') {
|
||||
if (directives.optOut != null) {
|
||||
logError(compileResult.error, programContext, fn.node.loc ?? null);
|
||||
} else {
|
||||
handleError(compileResult.error, programContext, fn.node.loc ?? null);
|
||||
}
|
||||
if (outputMode === 'client') {
|
||||
const retryResult = retryCompileFunction(fn, fnType, programContext);
|
||||
if (retryResult == null) {
|
||||
return null;
|
||||
}
|
||||
compiledFn = retryResult;
|
||||
} else {
|
||||
const retryResult = retryCompileFunction(fn, fnType, programContext);
|
||||
if (retryResult == null) {
|
||||
return null;
|
||||
}
|
||||
compiledFn = retryResult;
|
||||
} else {
|
||||
compiledFn = compileResult.compiledFn;
|
||||
}
|
||||
@@ -690,7 +663,7 @@ function processFn(
|
||||
|
||||
if (programContext.hasModuleScopeOptOut) {
|
||||
return null;
|
||||
} else if (programContext.opts.outputMode === 'lint') {
|
||||
} else if (programContext.opts.noEmit) {
|
||||
/**
|
||||
* inferEffectDependencies + noEmit is currently only used for linting. In
|
||||
* this mode, add source locations for where the compiler *can* infer effect
|
||||
@@ -720,7 +693,6 @@ function tryCompileFunction(
|
||||
fn: BabelFn,
|
||||
fnType: ReactFunctionType,
|
||||
programContext: ProgramContext,
|
||||
outputMode: CompilerOutputMode,
|
||||
):
|
||||
| {kind: 'compile'; compiledFn: CodegenFunction}
|
||||
| {kind: 'error'; error: unknown} {
|
||||
@@ -747,7 +719,7 @@ function tryCompileFunction(
|
||||
fn,
|
||||
programContext.opts.environment,
|
||||
fnType,
|
||||
outputMode,
|
||||
'all_features',
|
||||
programContext,
|
||||
programContext.opts.logger,
|
||||
programContext.filename,
|
||||
@@ -785,7 +757,7 @@ function retryCompileFunction(
|
||||
fn,
|
||||
environment,
|
||||
fnType,
|
||||
'client-no-memo',
|
||||
'no_inferred_memo',
|
||||
programContext,
|
||||
programContext.opts.logger,
|
||||
programContext.filename,
|
||||
|
||||
@@ -78,7 +78,7 @@ export function filterSuppressionsThatAffectFunction(
|
||||
|
||||
export function findProgramSuppressions(
|
||||
programComments: Array<t.Comment>,
|
||||
ruleNames: Array<string> | null,
|
||||
ruleNames: Array<string>,
|
||||
flowSuppressions: boolean,
|
||||
): Array<SuppressionRange> {
|
||||
const suppressionRanges: Array<SuppressionRange> = [];
|
||||
@@ -89,7 +89,7 @@ export function findProgramSuppressions(
|
||||
let disableNextLinePattern: RegExp | null = null;
|
||||
let disablePattern: RegExp | null = null;
|
||||
let enablePattern: RegExp | null = null;
|
||||
if (ruleNames != null && ruleNames.length !== 0) {
|
||||
if (ruleNames.length !== 0) {
|
||||
const rulePattern = `(${ruleNames.join('|')})`;
|
||||
disableNextLinePattern = new RegExp(
|
||||
`eslint-disable-next-line ${rulePattern}`,
|
||||
|
||||
@@ -4026,7 +4026,6 @@ function lowerAssignment(
|
||||
pattern: {
|
||||
kind: 'ArrayPattern',
|
||||
items,
|
||||
loc: lvalue.node.loc ?? GeneratedSource,
|
||||
},
|
||||
},
|
||||
value,
|
||||
@@ -4204,7 +4203,6 @@ function lowerAssignment(
|
||||
pattern: {
|
||||
kind: 'ObjectPattern',
|
||||
properties,
|
||||
loc: lvalue.node.loc ?? GeneratedSource,
|
||||
},
|
||||
},
|
||||
value,
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as t from '@babel/types';
|
||||
import {ZodError, z} from 'zod/v4';
|
||||
import {fromZodError} from 'zod-validation-error/v4';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {CompilerOutputMode, Logger, ProgramContext} from '../Entrypoint';
|
||||
import {Logger, ProgramContext} from '../Entrypoint';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
import {
|
||||
DEFAULT_GLOBALS,
|
||||
@@ -51,7 +51,6 @@ import {Scope as BabelScope, NodePath} from '@babel/traverse';
|
||||
import {TypeSchema} from './TypeSchema';
|
||||
import {FlowTypeEnv} from '../Flood/Types';
|
||||
import {defaultModuleTypeProvider} from './DefaultModuleTypeProvider';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
export const ReactElementSymbolSchema = z.object({
|
||||
elementSymbol: z.union([
|
||||
@@ -218,23 +217,6 @@ export const EnvironmentConfigSchema = z.object({
|
||||
*/
|
||||
validatePreserveExistingMemoizationGuarantees: z.boolean().default(true),
|
||||
|
||||
/**
|
||||
* Validate that dependencies supplied to manual memoization calls are exhaustive.
|
||||
*/
|
||||
validateExhaustiveMemoizationDependencies: z.boolean().default(true),
|
||||
|
||||
/**
|
||||
* Validate that dependencies supplied to effect hooks are exhaustive.
|
||||
* Can be:
|
||||
* - 'off': No validation (default)
|
||||
* - 'all': Validate and report both missing and extra dependencies
|
||||
* - 'missing-only': Only report missing dependencies
|
||||
* - 'extra-only': Only report extra/unnecessary dependencies
|
||||
*/
|
||||
validateExhaustiveEffectDependencies: z
|
||||
.enum(['off', 'all', 'missing-only', 'extra-only'])
|
||||
.default('off'),
|
||||
|
||||
/**
|
||||
* When this is true, rather than pruning existing manual memoization but ensuring or validating
|
||||
* that the memoized values remain memoized, the compiler will simply not prune existing calls to
|
||||
@@ -330,12 +312,6 @@ export const EnvironmentConfigSchema = z.object({
|
||||
*/
|
||||
validateNoSetStateInRender: z.boolean().default(true),
|
||||
|
||||
/**
|
||||
* When enabled, changes the behavior of validateNoSetStateInRender to recommend
|
||||
* using useKeyedState instead of the manual pattern for resetting state.
|
||||
*/
|
||||
enableUseKeyedState: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Validates that setState is not called synchronously within an effect (useEffect and friends).
|
||||
* Scheduling a setState (with an event listener, subscription, etc) is valid.
|
||||
@@ -696,27 +672,12 @@ export const EnvironmentConfigSchema = z.object({
|
||||
validateNoDynamicallyCreatedComponentsOrHooks: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* When enabled, allows setState calls in effects based on valid patterns involving refs:
|
||||
* - Allow setState where the value being set is derived from a ref. This is useful where
|
||||
* state needs to take into account layer information, and a layout effect reads layout
|
||||
* data from a ref and sets state.
|
||||
* - Allow conditionally calling setState after manually comparing previous/new values
|
||||
* for changes via a ref. Relying on effect deps is insufficient for non-primitive values,
|
||||
* so a ref is generally required to manually track previous values and compare prev/next
|
||||
* for meaningful changes before setting state.
|
||||
* When enabled, allows setState calls in effects when the value being set is
|
||||
* derived from a ref. This is useful for patterns where initial layout measurements
|
||||
* from refs need to be stored in state during mount.
|
||||
*/
|
||||
enableAllowSetStateFromRefsInEffects: z.boolean().default(true),
|
||||
|
||||
/**
|
||||
* When enabled, provides verbose error messages for setState calls within effects,
|
||||
* presenting multiple possible fixes to the user/agent since we cannot statically
|
||||
* determine which specific use-case applies:
|
||||
* 1. Non-local derived data - requires restructuring state ownership
|
||||
* 2. Derived event pattern - detecting when a prop changes
|
||||
* 3. Force update / external sync - should use useSyncExternalStore
|
||||
*/
|
||||
enableVerboseNoSetStateInEffect: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enables inference of event handler types for JSX props on built-in DOM elements.
|
||||
* When enabled, functions passed to event handler props (props starting with "on")
|
||||
@@ -764,7 +725,7 @@ export class Environment {
|
||||
code: string | null;
|
||||
config: EnvironmentConfig;
|
||||
fnType: ReactFunctionType;
|
||||
outputMode: CompilerOutputMode;
|
||||
compilerMode: CompilerMode;
|
||||
programContext: ProgramContext;
|
||||
hasFireRewrite: boolean;
|
||||
hasInferredEffect: boolean;
|
||||
@@ -779,7 +740,7 @@ export class Environment {
|
||||
constructor(
|
||||
scope: BabelScope,
|
||||
fnType: ReactFunctionType,
|
||||
outputMode: CompilerOutputMode,
|
||||
compilerMode: CompilerMode,
|
||||
config: EnvironmentConfig,
|
||||
contextIdentifiers: Set<t.Identifier>,
|
||||
parentFunction: NodePath<t.Function>, // the outermost function being compiled
|
||||
@@ -790,7 +751,7 @@ export class Environment {
|
||||
) {
|
||||
this.#scope = scope;
|
||||
this.fnType = fnType;
|
||||
this.outputMode = outputMode;
|
||||
this.compilerMode = compilerMode;
|
||||
this.config = config;
|
||||
this.filename = filename;
|
||||
this.code = code;
|
||||
@@ -886,65 +847,8 @@ export class Environment {
|
||||
return this.#flowTypeEnvironment;
|
||||
}
|
||||
|
||||
get enableDropManualMemoization(): boolean {
|
||||
switch (this.outputMode) {
|
||||
case 'lint': {
|
||||
// linting drops to be more compatible with compiler analysis
|
||||
return true;
|
||||
}
|
||||
case 'client':
|
||||
case 'ssr': {
|
||||
return true;
|
||||
}
|
||||
case 'client-no-memo': {
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
this.outputMode,
|
||||
`Unexpected output mode '${this.outputMode}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get enableMemoization(): boolean {
|
||||
switch (this.outputMode) {
|
||||
case 'client':
|
||||
case 'lint': {
|
||||
// linting also enables memoization so that we can check if manual memoization is preserved
|
||||
return true;
|
||||
}
|
||||
case 'ssr':
|
||||
case 'client-no-memo': {
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
this.outputMode,
|
||||
`Unexpected output mode '${this.outputMode}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get enableValidations(): boolean {
|
||||
switch (this.outputMode) {
|
||||
case 'client':
|
||||
case 'lint':
|
||||
case 'ssr': {
|
||||
return true;
|
||||
}
|
||||
case 'client-no-memo': {
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
this.outputMode,
|
||||
`Unexpected output mode '${this.outputMode}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
get isInferredMemoEnabled(): boolean {
|
||||
return this.compilerMode !== 'no_inferred_memo';
|
||||
}
|
||||
|
||||
get nextIdentifierId(): IdentifierId {
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
BuiltInUseInsertionEffectHookId,
|
||||
BuiltInUseLayoutEffectHookId,
|
||||
BuiltInUseOperatorId,
|
||||
BuiltInUseOptimisticId,
|
||||
BuiltInUseReducerId,
|
||||
BuiltInUseRefId,
|
||||
BuiltInUseStateId,
|
||||
@@ -819,18 +818,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'useOptimistic',
|
||||
addHook(DEFAULT_SHAPES, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInUseOptimisticId},
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'useOptimistic',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
returnValueReason: ValueReason.State,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'use',
|
||||
addFunction(
|
||||
|
||||
@@ -694,13 +694,11 @@ export type SpreadPattern = {
|
||||
export type ArrayPattern = {
|
||||
kind: 'ArrayPattern';
|
||||
items: Array<Place | SpreadPattern | Hole>;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
export type ObjectPattern = {
|
||||
kind: 'ObjectPattern';
|
||||
properties: Array<ObjectProperty | SpreadPattern>;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
export type ObjectPropertyKey =
|
||||
@@ -805,11 +803,9 @@ export type ManualMemoDependency = {
|
||||
| {
|
||||
kind: 'NamedLocal';
|
||||
value: Place;
|
||||
constant: boolean;
|
||||
}
|
||||
| {kind: 'Global'; identifierName: string};
|
||||
path: DependencyPath;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
export type StartMemoize = {
|
||||
@@ -821,11 +817,6 @@ export type StartMemoize = {
|
||||
* (e.g. useMemo without a second arg)
|
||||
*/
|
||||
deps: Array<ManualMemoDependency> | null;
|
||||
/**
|
||||
* The source location of the dependencies argument. Used for
|
||||
* emitting diagnostics with a suggested replacement
|
||||
*/
|
||||
depsLoc: SourceLocation | null;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
export type FinishMemoize = {
|
||||
@@ -1689,28 +1680,6 @@ export function areEqualPaths(a: DependencyPath, b: DependencyPath): boolean {
|
||||
)
|
||||
);
|
||||
}
|
||||
export function isSubPath(
|
||||
subpath: DependencyPath,
|
||||
path: DependencyPath,
|
||||
): boolean {
|
||||
return (
|
||||
subpath.length <= path.length &&
|
||||
subpath.every(
|
||||
(item, ix) =>
|
||||
item.property === path[ix].property &&
|
||||
item.optional === path[ix].optional,
|
||||
)
|
||||
);
|
||||
}
|
||||
export function isSubPathIgnoringOptionals(
|
||||
subpath: DependencyPath,
|
||||
path: DependencyPath,
|
||||
): boolean {
|
||||
return (
|
||||
subpath.length <= path.length &&
|
||||
subpath.every((item, ix) => item.property === path[ix].property)
|
||||
);
|
||||
}
|
||||
|
||||
export function getPlaceScope(
|
||||
id: InstructionId,
|
||||
@@ -1854,10 +1823,6 @@ export function isPrimitiveType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Primitive';
|
||||
}
|
||||
|
||||
export function isPlainObjectType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInObject';
|
||||
}
|
||||
|
||||
export function isArrayType(id: Identifier): boolean {
|
||||
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
|
||||
}
|
||||
@@ -1922,18 +1887,6 @@ export function isStartTransitionType(id: Identifier): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function isUseOptimisticType(id: Identifier): boolean {
|
||||
return (
|
||||
id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseOptimistic'
|
||||
);
|
||||
}
|
||||
|
||||
export function isSetOptimisticType(id: Identifier): boolean {
|
||||
return (
|
||||
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetOptimistic'
|
||||
);
|
||||
}
|
||||
|
||||
export function isSetActionStateType(id: Identifier): boolean {
|
||||
return (
|
||||
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState'
|
||||
@@ -1967,8 +1920,7 @@ export function isStableType(id: Identifier): boolean {
|
||||
isSetActionStateType(id) ||
|
||||
isDispatcherType(id) ||
|
||||
isUseRefType(id) ||
|
||||
isStartTransitionType(id) ||
|
||||
isSetOptimisticType(id)
|
||||
isStartTransitionType(id)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1979,9 +1931,8 @@ export function isStableTypeContainer(id: Identifier): boolean {
|
||||
}
|
||||
return (
|
||||
isUseStateType(id) || // setState
|
||||
isUseActionStateType(id) || // setActionState
|
||||
type_.shapeId === 'BuiltInUseActionState' || // setActionState
|
||||
isUseReducerType(id) || // dispatcher
|
||||
isUseOptimisticType(id) || // setOptimistic
|
||||
type_.shapeId === 'BuiltInUseTransition' // startTransition
|
||||
);
|
||||
}
|
||||
@@ -2001,7 +1952,6 @@ export function evaluatesToStableTypeOrContainer(
|
||||
case 'useActionState':
|
||||
case 'useRef':
|
||||
case 'useTransition':
|
||||
case 'useOptimistic':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2025,11 +1975,6 @@ export function isUseInsertionEffectHookType(id: Identifier): boolean {
|
||||
id.type.shapeId === 'BuiltInUseInsertionEffectHook'
|
||||
);
|
||||
}
|
||||
export function isUseEffectEventType(id: Identifier): boolean {
|
||||
return (
|
||||
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectEvent'
|
||||
);
|
||||
}
|
||||
|
||||
export function isUseContextHookType(id: Identifier): boolean {
|
||||
return (
|
||||
|
||||
@@ -304,7 +304,6 @@ export type HookKind =
|
||||
| 'useTransition'
|
||||
| 'useImperativeHandle'
|
||||
| 'useEffectEvent'
|
||||
| 'useOptimistic'
|
||||
| 'Custom';
|
||||
|
||||
/*
|
||||
@@ -400,8 +399,6 @@ export const BuiltInUseReducerId = 'BuiltInUseReducer';
|
||||
export const BuiltInDispatchId = 'BuiltInDispatch';
|
||||
export const BuiltInUseContextHookId = 'BuiltInUseContextHook';
|
||||
export const BuiltInUseTransitionId = 'BuiltInUseTransition';
|
||||
export const BuiltInUseOptimisticId = 'BuiltInUseOptimistic';
|
||||
export const BuiltInSetOptimisticId = 'BuiltInSetOptimistic';
|
||||
export const BuiltInStartTransitionId = 'BuiltInStartTransition';
|
||||
export const BuiltInFireId = 'BuiltInFire';
|
||||
export const BuiltInFireFunctionId = 'BuiltInFireFunction';
|
||||
@@ -1189,25 +1186,6 @@ addObject(BUILTIN_SHAPES, BuiltInUseTransitionId, [
|
||||
],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInUseOptimisticId, [
|
||||
['0', {kind: 'Poly'}],
|
||||
[
|
||||
'1',
|
||||
addFunction(
|
||||
BUILTIN_SHAPES,
|
||||
[],
|
||||
{
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: PRIMITIVE_TYPE,
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
},
|
||||
BuiltInSetOptimisticId,
|
||||
),
|
||||
],
|
||||
]);
|
||||
|
||||
addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
|
||||
['0', {kind: 'Poly'}],
|
||||
[
|
||||
|
||||
@@ -42,7 +42,7 @@ type IdentifierSidemap = {
|
||||
functions: Map<IdentifierId, TInstruction<FunctionExpression>>;
|
||||
manualMemos: Map<IdentifierId, ManualMemoCallee>;
|
||||
react: Set<IdentifierId>;
|
||||
maybeDepsLists: Map<IdentifierId, {loc: SourceLocation; deps: Array<Place>}>;
|
||||
maybeDepsLists: Map<IdentifierId, Array<Place>>;
|
||||
maybeDeps: Map<IdentifierId, ManualMemoDependency>;
|
||||
optionals: Set<IdentifierId>;
|
||||
};
|
||||
@@ -65,7 +65,6 @@ export function collectMaybeMemoDependencies(
|
||||
identifierName: value.binding.name,
|
||||
},
|
||||
path: [],
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
@@ -75,7 +74,6 @@ export function collectMaybeMemoDependencies(
|
||||
root: object.root,
|
||||
// TODO: determine if the access is optional
|
||||
path: [...object.path, {property: value.property, optional}],
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
break;
|
||||
@@ -94,10 +92,8 @@ export function collectMaybeMemoDependencies(
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: {...value.place},
|
||||
constant: false,
|
||||
},
|
||||
path: [],
|
||||
loc: value.place.loc,
|
||||
};
|
||||
}
|
||||
break;
|
||||
@@ -163,10 +159,10 @@ function collectTemporaries(
|
||||
}
|
||||
case 'ArrayExpression': {
|
||||
if (value.elements.every(e => e.kind === 'Identifier')) {
|
||||
sidemap.maybeDepsLists.set(instr.lvalue.identifier.id, {
|
||||
loc: value.loc,
|
||||
deps: value.elements as Array<Place>,
|
||||
});
|
||||
sidemap.maybeDepsLists.set(
|
||||
instr.lvalue.identifier.id,
|
||||
value.elements as Array<Place>,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -186,7 +182,6 @@ function makeManualMemoizationMarkers(
|
||||
fnExpr: Place,
|
||||
env: Environment,
|
||||
depsList: Array<ManualMemoDependency> | null,
|
||||
depsLoc: SourceLocation | null,
|
||||
memoDecl: Place,
|
||||
manualMemoId: number,
|
||||
): [TInstruction<StartMemoize>, TInstruction<FinishMemoize>] {
|
||||
@@ -202,7 +197,6 @@ function makeManualMemoizationMarkers(
|
||||
* as dependencies
|
||||
*/
|
||||
deps: depsList,
|
||||
depsLoc,
|
||||
loc: fnExpr.loc,
|
||||
},
|
||||
effects: null,
|
||||
@@ -293,85 +287,86 @@ function extractManualMemoizationArgs(
|
||||
sidemap: IdentifierSidemap,
|
||||
errors: CompilerError,
|
||||
): {
|
||||
fnPlace: Place;
|
||||
fnPlace: Place | null;
|
||||
depsList: Array<ManualMemoDependency> | null;
|
||||
depsLoc: SourceLocation | null;
|
||||
} | null {
|
||||
} {
|
||||
const [fnPlace, depsListPlace] = instr.value.args as Array<
|
||||
Place | SpreadPattern | undefined
|
||||
>;
|
||||
if (fnPlace == null || fnPlace.kind !== 'Identifier') {
|
||||
if (fnPlace == null) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected a callback function to be passed to ${kind}`,
|
||||
description:
|
||||
kind === 'useCallback'
|
||||
? 'The first argument to useCallback() must be a function to cache'
|
||||
: 'The first argument to useMemo() must be a function that calculates a result to cache',
|
||||
description: `Expected a callback function to be passed to ${kind}`,
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: instr.value.loc,
|
||||
message:
|
||||
kind === 'useCallback'
|
||||
? `Expected a callback function`
|
||||
: `Expected a memoization function`,
|
||||
message: `Expected a callback function to be passed to ${kind}`,
|
||||
}),
|
||||
);
|
||||
return null;
|
||||
return {fnPlace: null, depsList: null};
|
||||
}
|
||||
if (depsListPlace == null) {
|
||||
return {
|
||||
fnPlace,
|
||||
depsList: null,
|
||||
depsLoc: null,
|
||||
};
|
||||
}
|
||||
const maybeDepsList =
|
||||
depsListPlace.kind === 'Identifier'
|
||||
? sidemap.maybeDepsLists.get(depsListPlace.identifier.id)
|
||||
: null;
|
||||
if (maybeDepsList == null) {
|
||||
if (fnPlace.kind === 'Spread' || depsListPlace?.kind === 'Spread') {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
description: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
reason: `Unexpected spread argument to ${kind}`,
|
||||
description: `Unexpected spread argument to ${kind}`,
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc:
|
||||
depsListPlace?.kind === 'Identifier' ? depsListPlace.loc : instr.loc,
|
||||
message: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
loc: instr.value.loc,
|
||||
message: `Unexpected spread argument to ${kind}`,
|
||||
}),
|
||||
);
|
||||
return null;
|
||||
return {fnPlace: null, depsList: null};
|
||||
}
|
||||
const depsList: Array<ManualMemoDependency> = [];
|
||||
for (const dep of maybeDepsList.deps) {
|
||||
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
|
||||
if (maybeDep == null) {
|
||||
let depsList: Array<ManualMemoDependency> | null = null;
|
||||
if (depsListPlace != null) {
|
||||
const maybeDepsList = sidemap.maybeDepsLists.get(
|
||||
depsListPlace.identifier.id,
|
||||
);
|
||||
if (maybeDepsList == null) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
reason: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
description: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: dep.loc,
|
||||
message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
loc: depsListPlace.loc,
|
||||
message: `Expected the dependency list for ${kind} to be an array literal`,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
depsList.push(maybeDep);
|
||||
return {fnPlace, depsList: null};
|
||||
}
|
||||
depsList = [];
|
||||
for (const dep of maybeDepsList) {
|
||||
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
|
||||
if (maybeDep == null) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.UseMemo,
|
||||
reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
description: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: dep.loc,
|
||||
message: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
depsList.push(maybeDep);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
fnPlace,
|
||||
depsList,
|
||||
depsLoc: maybeDepsList.loc,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -432,17 +427,16 @@ export function dropManualMemoization(
|
||||
|
||||
const manualMemo = sidemap.manualMemos.get(id);
|
||||
if (manualMemo != null) {
|
||||
const memoDetails = extractManualMemoizationArgs(
|
||||
const {fnPlace, depsList} = extractManualMemoizationArgs(
|
||||
instr as TInstruction<CallExpression> | TInstruction<MethodCall>,
|
||||
manualMemo.kind,
|
||||
sidemap,
|
||||
errors,
|
||||
);
|
||||
|
||||
if (memoDetails == null) {
|
||||
if (fnPlace == null) {
|
||||
continue;
|
||||
}
|
||||
const {fnPlace, depsList, depsLoc} = memoDetails;
|
||||
|
||||
instr.value = getManualMemoizationReplacement(
|
||||
fnPlace,
|
||||
@@ -493,7 +487,6 @@ export function dropManualMemoization(
|
||||
fnPlace,
|
||||
func.env,
|
||||
depsList,
|
||||
depsLoc,
|
||||
memoDecl,
|
||||
nextManualMemoId++,
|
||||
);
|
||||
|
||||
@@ -2452,7 +2452,7 @@ function computeEffectsForLegacySignature(
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (signature.knownIncompatible != null && state.env.enableValidations) {
|
||||
if (signature.knownIncompatible != null && state.env.isInferredMemoEnabled) {
|
||||
const errors = new CompilerError();
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
|
||||
@@ -609,19 +609,6 @@ function evaluateInstruction(
|
||||
constantPropagationImpl(value.loweredFunc.func, constants);
|
||||
return null;
|
||||
}
|
||||
case 'StartMemoize': {
|
||||
if (value.deps != null) {
|
||||
for (const dep of value.deps) {
|
||||
if (dep.root.kind === 'NamedLocal') {
|
||||
const placeValue = read(constants, dep.root.value);
|
||||
if (placeValue != null && placeValue.kind === 'Primitive') {
|
||||
dep.root.constant = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
// TODO: handle more cases
|
||||
return null;
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
|
||||
import {
|
||||
BlockId,
|
||||
Environment,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
@@ -70,14 +68,9 @@ export function deadCodeElimination(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
class State {
|
||||
env: Environment;
|
||||
named: Set<string> = new Set();
|
||||
identifiers: Set<IdentifierId> = new Set();
|
||||
|
||||
constructor(env: Environment) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
// Mark the identifier as being referenced (not dead code)
|
||||
reference(identifier: Identifier): void {
|
||||
this.identifiers.add(identifier.id);
|
||||
@@ -119,7 +112,7 @@ function findReferencedIdentifiers(fn: HIRFunction): State {
|
||||
const hasLoop = hasBackEdge(fn);
|
||||
const reversedBlocks = [...fn.body.blocks.values()].reverse();
|
||||
|
||||
const state = new State(fn.env);
|
||||
const state = new State();
|
||||
let size = state.count;
|
||||
do {
|
||||
size = state.count;
|
||||
@@ -317,27 +310,12 @@ function pruneableValue(value: InstructionValue, state: State): boolean {
|
||||
// explicitly retain debugger statements to not break debugging workflows
|
||||
return false;
|
||||
}
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
if (state.env.outputMode === 'ssr') {
|
||||
const calleee =
|
||||
value.kind === 'CallExpression' ? value.callee : value.property;
|
||||
const hookKind = getHookKind(state.env, calleee.identifier);
|
||||
switch (hookKind) {
|
||||
case 'useState':
|
||||
case 'useReducer':
|
||||
case 'useRef': {
|
||||
// unused refs can be removed
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case 'Await':
|
||||
case 'CallExpression':
|
||||
case 'ComputedDelete':
|
||||
case 'ComputedStore':
|
||||
case 'PropertyDelete':
|
||||
case 'MethodCall':
|
||||
case 'PropertyStore':
|
||||
case 'StoreGlobal': {
|
||||
/*
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError} from '..';
|
||||
import {
|
||||
CallExpression,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
InstructionValue,
|
||||
isArrayType,
|
||||
isPlainObjectType,
|
||||
isPrimitiveType,
|
||||
isSetStateType,
|
||||
isStartTransitionType,
|
||||
LoadLocal,
|
||||
StoreLocal,
|
||||
} from '../HIR';
|
||||
import {
|
||||
eachInstructionValueOperand,
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {retainWhere} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
* Optimizes the code for running specifically in an SSR environment. This optimization
|
||||
* asssumes that setState will not be called during render during initial mount, which
|
||||
* allows inlining useState/useReducer.
|
||||
*
|
||||
* Optimizations:
|
||||
* - Inline useState/useReducer
|
||||
* - Remove effects
|
||||
* - Remove refs where known to be unused during render (eg directly passed to a dom node)
|
||||
* - Remove event handlers
|
||||
*
|
||||
* Note that an earlier pass already inlines useMemo/useCallback
|
||||
*/
|
||||
export function optimizeForSSR(fn: HIRFunction): void {
|
||||
const inlinedState = new Map<IdentifierId, InstructionValue>();
|
||||
/**
|
||||
* First pass identifies useState/useReducer which can be safely inlined. Any use
|
||||
* of the hook return other than destructuring (with a specific pattern) prevents
|
||||
* inlining.
|
||||
*
|
||||
* Supported cases:
|
||||
* - `const [state, ] = useState( <primitive-array-or-object> )`
|
||||
* - `const [state, ] = useReducer(..., <value>)`
|
||||
* - `const [state, ] = useReducer[..., <value>, <init>]`
|
||||
*/
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const {value} = instr;
|
||||
switch (value.kind) {
|
||||
case 'Destructure': {
|
||||
if (
|
||||
inlinedState.has(value.value.identifier.id) &&
|
||||
value.lvalue.pattern.kind === 'ArrayPattern' &&
|
||||
value.lvalue.pattern.items.length >= 1 &&
|
||||
value.lvalue.pattern.items[0].kind === 'Identifier'
|
||||
) {
|
||||
// Allow destructuring of inlined states
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MethodCall':
|
||||
case 'CallExpression': {
|
||||
const calleee =
|
||||
value.kind === 'CallExpression' ? value.callee : value.property;
|
||||
const hookKind = getHookKind(fn.env, calleee.identifier);
|
||||
switch (hookKind) {
|
||||
case 'useReducer': {
|
||||
if (
|
||||
value.args.length === 2 &&
|
||||
value.args[1].kind === 'Identifier'
|
||||
) {
|
||||
const arg = value.args[1];
|
||||
const replace: LoadLocal = {
|
||||
kind: 'LoadLocal',
|
||||
place: arg,
|
||||
loc: arg.loc,
|
||||
};
|
||||
inlinedState.set(instr.lvalue.identifier.id, replace);
|
||||
} else if (
|
||||
value.args.length === 3 &&
|
||||
value.args[1].kind === 'Identifier' &&
|
||||
value.args[2].kind === 'Identifier'
|
||||
) {
|
||||
const arg = value.args[1];
|
||||
const initializer = value.args[2];
|
||||
const replace: CallExpression = {
|
||||
kind: 'CallExpression',
|
||||
callee: initializer,
|
||||
args: [arg],
|
||||
loc: value.loc,
|
||||
};
|
||||
inlinedState.set(instr.lvalue.identifier.id, replace);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'useState': {
|
||||
if (
|
||||
value.args.length === 1 &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const arg = value.args[0];
|
||||
if (
|
||||
isPrimitiveType(arg.identifier) ||
|
||||
isPlainObjectType(arg.identifier) ||
|
||||
isArrayType(arg.identifier)
|
||||
) {
|
||||
const replace: LoadLocal = {
|
||||
kind: 'LoadLocal',
|
||||
place: arg,
|
||||
loc: arg.loc,
|
||||
};
|
||||
inlinedState.set(instr.lvalue.identifier.id, replace);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Any use of useState/useReducer return besides destructuring prevents inlining
|
||||
if (inlinedState.size !== 0) {
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
inlinedState.delete(operand.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inlinedState.size !== 0) {
|
||||
for (const operand of eachTerminalOperand(block.terminal)) {
|
||||
inlinedState.delete(operand.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const {value} = instr;
|
||||
switch (value.kind) {
|
||||
case 'FunctionExpression': {
|
||||
if (hasKnownNonRenderCall(value.loweredFunc.func)) {
|
||||
instr.value = {
|
||||
kind: 'Primitive',
|
||||
value: undefined,
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'JsxExpression': {
|
||||
if (
|
||||
value.tag.kind === 'BuiltinTag' &&
|
||||
value.tag.name.indexOf('-') === -1
|
||||
) {
|
||||
const tag = value.tag.name;
|
||||
retainWhere(value.props, prop => {
|
||||
return (
|
||||
prop.kind === 'JsxSpreadAttribute' ||
|
||||
(!isKnownEventHandler(tag, prop.name) && prop.name !== 'ref')
|
||||
);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Destructure': {
|
||||
if (inlinedState.has(value.value.identifier.id)) {
|
||||
// Canonical check is part of determining if state can inline, this is for TS
|
||||
CompilerError.invariant(
|
||||
value.lvalue.pattern.kind === 'ArrayPattern' &&
|
||||
value.lvalue.pattern.items.length >= 1 &&
|
||||
value.lvalue.pattern.items[0].kind === 'Identifier',
|
||||
{
|
||||
reason:
|
||||
'Expected a valid destructuring pattern for inlined state',
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
message: 'Expected a valid destructuring pattern',
|
||||
loc: value.loc,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
const store: StoreLocal = {
|
||||
kind: 'StoreLocal',
|
||||
loc: value.loc,
|
||||
type: null,
|
||||
lvalue: {
|
||||
kind: value.lvalue.kind,
|
||||
place: value.lvalue.pattern.items[0],
|
||||
},
|
||||
value: value.value,
|
||||
};
|
||||
instr.value = store;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MethodCall':
|
||||
case 'CallExpression': {
|
||||
const calleee =
|
||||
value.kind === 'CallExpression' ? value.callee : value.property;
|
||||
const hookKind = getHookKind(fn.env, calleee.identifier);
|
||||
switch (hookKind) {
|
||||
case 'useEffectEvent': {
|
||||
if (
|
||||
value.args.length === 1 &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const load: LoadLocal = {
|
||||
kind: 'LoadLocal',
|
||||
place: value.args[0],
|
||||
loc: value.loc,
|
||||
};
|
||||
instr.value = load;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'useEffect':
|
||||
case 'useLayoutEffect':
|
||||
case 'useInsertionEffect': {
|
||||
// Drop effects
|
||||
instr.value = {
|
||||
kind: 'Primitive',
|
||||
value: undefined,
|
||||
loc: value.loc,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'useReducer':
|
||||
case 'useState': {
|
||||
const replace = inlinedState.get(instr.lvalue.identifier.id);
|
||||
if (replace != null) {
|
||||
instr.value = replace;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasKnownNonRenderCall(fn: HIRFunction): boolean {
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'CallExpression' &&
|
||||
(isSetStateType(instr.value.callee.identifier) ||
|
||||
isStartTransitionType(instr.value.callee.identifier))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const EVENT_HANDLER_PATTERN = /^on[A-Z]/;
|
||||
function isKnownEventHandler(_tag: string, prop: string): boolean {
|
||||
return EVENT_HANDLER_PATTERN.test(prop);
|
||||
}
|
||||
@@ -515,7 +515,6 @@ function emitDestructureProps(
|
||||
pattern: {
|
||||
kind: 'ObjectPattern',
|
||||
properties,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
kind: InstructionKind.Let,
|
||||
},
|
||||
|
||||
@@ -159,7 +159,7 @@ export function codegenFunction(
|
||||
const compiled = compileResult.unwrap();
|
||||
|
||||
const hookGuard = fn.env.config.enableEmitHookGuards;
|
||||
if (hookGuard != null && fn.env.outputMode === 'client') {
|
||||
if (hookGuard != null && fn.env.isInferredMemoEnabled) {
|
||||
compiled.body = t.blockStatement([
|
||||
createHookGuard(
|
||||
hookGuard,
|
||||
@@ -259,7 +259,7 @@ export function codegenFunction(
|
||||
if (
|
||||
emitInstrumentForget != null &&
|
||||
fn.id != null &&
|
||||
fn.env.outputMode === 'client'
|
||||
fn.env.isInferredMemoEnabled
|
||||
) {
|
||||
/*
|
||||
* Technically, this is a conditional hook call. However, we expect
|
||||
@@ -591,10 +591,7 @@ function codegenBlockNoReset(
|
||||
}
|
||||
|
||||
function wrapCacheDep(cx: Context, value: t.Expression): t.Expression {
|
||||
if (
|
||||
cx.env.config.enableEmitFreeze != null &&
|
||||
cx.env.outputMode === 'client'
|
||||
) {
|
||||
if (cx.env.config.enableEmitFreeze != null && cx.env.isInferredMemoEnabled) {
|
||||
const emitFreezeIdentifier = cx.env.programContext.addImportSpecifier(
|
||||
cx.env.config.enableEmitFreeze,
|
||||
).name;
|
||||
@@ -702,7 +699,7 @@ function codegenReactiveScope(
|
||||
outputComments.push(name.name);
|
||||
if (!cx.hasDeclared(identifier)) {
|
||||
statements.push(
|
||||
t.variableDeclaration('let', [createVariableDeclarator(name, null)]),
|
||||
t.variableDeclaration('let', [t.variableDeclarator(name)]),
|
||||
);
|
||||
}
|
||||
cacheLoads.push({name, index, value: wrapCacheDep(cx, name)});
|
||||
@@ -1387,7 +1384,7 @@ function codegenInstructionNullable(
|
||||
suggestions: null,
|
||||
});
|
||||
return createVariableDeclaration(instr.loc, 'const', [
|
||||
createVariableDeclarator(codegenLValue(cx, lvalue), value),
|
||||
t.variableDeclarator(codegenLValue(cx, lvalue), value),
|
||||
]);
|
||||
}
|
||||
case InstructionKind.Function: {
|
||||
@@ -1451,7 +1448,7 @@ function codegenInstructionNullable(
|
||||
suggestions: null,
|
||||
});
|
||||
return createVariableDeclaration(instr.loc, 'let', [
|
||||
createVariableDeclarator(codegenLValue(cx, lvalue), value),
|
||||
t.variableDeclarator(codegenLValue(cx, lvalue), value),
|
||||
]);
|
||||
}
|
||||
case InstructionKind.Reassign: {
|
||||
@@ -1691,9 +1688,6 @@ function withLoc<T extends (...args: Array<any>) => t.Node>(
|
||||
};
|
||||
}
|
||||
|
||||
const createIdentifier = withLoc(t.identifier);
|
||||
const createArrayPattern = withLoc(t.arrayPattern);
|
||||
const createObjectPattern = withLoc(t.objectPattern);
|
||||
const createBinaryExpression = withLoc(t.binaryExpression);
|
||||
const createExpressionStatement = withLoc(t.expressionStatement);
|
||||
const _createLabelledStatement = withLoc(t.labeledStatement);
|
||||
@@ -1725,31 +1719,6 @@ const createTryStatement = withLoc(t.tryStatement);
|
||||
const createBreakStatement = withLoc(t.breakStatement);
|
||||
const createContinueStatement = withLoc(t.continueStatement);
|
||||
|
||||
function createVariableDeclarator(
|
||||
id: t.LVal,
|
||||
init?: t.Expression | null,
|
||||
): t.VariableDeclarator {
|
||||
const node = t.variableDeclarator(id, init);
|
||||
|
||||
/*
|
||||
* The variable declarator location is not preserved in HIR, however, we can use the
|
||||
* start location of the id and the end location of the init to recreate the
|
||||
* exact original variable declarator location.
|
||||
*
|
||||
* Or if init is null, we likely have a declaration without an initializer, so we can use the id.loc.end as the end location.
|
||||
*/
|
||||
if (id.loc && (init === null || init?.loc)) {
|
||||
node.loc = {
|
||||
start: id.loc.start,
|
||||
end: init?.loc?.end ?? id.loc.end,
|
||||
filename: id.loc.filename,
|
||||
identifierName: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function createHookGuard(
|
||||
guard: ExternalFunction,
|
||||
context: ProgramContext,
|
||||
@@ -1803,7 +1772,7 @@ function createCallExpression(
|
||||
}
|
||||
|
||||
const hookGuard = env.config.enableEmitHookGuards;
|
||||
if (hookGuard != null && isHook && env.outputMode === 'client') {
|
||||
if (hookGuard != null && isHook && env.isInferredMemoEnabled) {
|
||||
const iife = t.functionExpression(
|
||||
null,
|
||||
[],
|
||||
@@ -1857,7 +1826,7 @@ function codegenInstruction(
|
||||
);
|
||||
} else {
|
||||
return createVariableDeclaration(instr.loc, 'const', [
|
||||
createVariableDeclarator(
|
||||
t.variableDeclarator(
|
||||
convertIdentifier(instr.lvalue.identifier),
|
||||
expressionValue,
|
||||
),
|
||||
@@ -2784,7 +2753,7 @@ function codegenArrayPattern(
|
||||
): t.ArrayPattern {
|
||||
const hasHoles = !pattern.items.every(e => e.kind !== 'Hole');
|
||||
if (hasHoles) {
|
||||
const result = createArrayPattern(pattern.loc, []);
|
||||
const result = t.arrayPattern([]);
|
||||
/*
|
||||
* Older versions of babel have a validation bug fixed by
|
||||
* https://github.com/babel/babel/pull/10917
|
||||
@@ -2805,8 +2774,7 @@ function codegenArrayPattern(
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return createArrayPattern(
|
||||
pattern.loc,
|
||||
return t.arrayPattern(
|
||||
pattern.items.map(item => {
|
||||
if (item.kind === 'Hole') {
|
||||
return null;
|
||||
@@ -2826,8 +2794,7 @@ function codegenLValue(
|
||||
return codegenArrayPattern(cx, pattern);
|
||||
}
|
||||
case 'ObjectPattern': {
|
||||
return createObjectPattern(
|
||||
pattern.loc,
|
||||
return t.objectPattern(
|
||||
pattern.properties.map(property => {
|
||||
if (property.kind === 'ObjectProperty') {
|
||||
const key = codegenObjectPropertyKey(cx, property.key);
|
||||
@@ -2946,7 +2913,7 @@ function convertIdentifier(identifier: Identifier): t.Identifier {
|
||||
suggestions: null,
|
||||
},
|
||||
);
|
||||
return createIdentifier(identifier.loc, identifier.name.value);
|
||||
return t.identifier(identifier.name.value);
|
||||
}
|
||||
|
||||
function compareScopeDependency(
|
||||
|
||||
@@ -389,6 +389,14 @@ export function findDisjointMutableValues(
|
||||
*/
|
||||
operand.identifier.mutableRange.start > 0
|
||||
) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
if (operand.identifier.type.kind === 'Primitive') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
operands.push(operand.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,8 +52,6 @@ type ValidationContext = {
|
||||
readonly setStateUsages: Map<IdentifierId, Set<SourceLocation>>;
|
||||
};
|
||||
|
||||
const MAX_FIXPOINT_ITERATIONS = 100;
|
||||
|
||||
class DerivationCache {
|
||||
hasChanges: boolean = false;
|
||||
cache: Map<IdentifierId, DerivationMetadata> = new Map();
|
||||
@@ -226,7 +224,6 @@ export function validateNoDerivedComputationsInEffects_exp(
|
||||
}
|
||||
|
||||
let isFirstPass = true;
|
||||
let iterationCount = 0;
|
||||
do {
|
||||
context.derivationCache.takeSnapshot();
|
||||
|
||||
@@ -239,19 +236,6 @@ export function validateNoDerivedComputationsInEffects_exp(
|
||||
|
||||
context.derivationCache.checkForChanges();
|
||||
isFirstPass = false;
|
||||
iterationCount++;
|
||||
CompilerError.invariant(iterationCount < MAX_FIXPOINT_ITERATIONS, {
|
||||
reason:
|
||||
'[ValidateNoDerivedComputationsInEffects] Fixpoint iteration failed to converge.',
|
||||
description: `Fixpoint iteration exceeded ${MAX_FIXPOINT_ITERATIONS} iterations while tracking derivations. This suggests a cyclic dependency in the derivation cache.`,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: fn.loc,
|
||||
message: `Exceeded ${MAX_FIXPOINT_ITERATIONS} iterations in ValidateNoDerivedComputationsInEffects`,
|
||||
},
|
||||
],
|
||||
});
|
||||
} while (context.derivationCache.snapshot());
|
||||
|
||||
for (const [, effect] of effectsCache) {
|
||||
@@ -388,7 +372,7 @@ function recordInstructionDerivations(
|
||||
dependencies: deps,
|
||||
});
|
||||
}
|
||||
} else if (isUseStateType(lvalue.identifier)) {
|
||||
} else if (isUseStateType(lvalue.identifier) && value.args.length > 0) {
|
||||
typeOfValue = 'fromState';
|
||||
context.derivationCache.addDerivationEntry(
|
||||
lvalue,
|
||||
@@ -438,14 +422,6 @@ function recordInstructionDerivations(
|
||||
);
|
||||
}
|
||||
|
||||
if (value.kind === 'FunctionExpression') {
|
||||
/*
|
||||
* We don't want to record effect mutations of FunctionExpressions the mutations will happen in the
|
||||
* function body and we will record them there.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
switch (operand.effect) {
|
||||
case Effect.Capture:
|
||||
@@ -536,19 +512,6 @@ function buildTreeNode(
|
||||
|
||||
const namedSiblings: Set<string> = new Set();
|
||||
for (const childId of sourceMetadata.sourcesIds) {
|
||||
CompilerError.invariant(childId !== sourceId, {
|
||||
reason:
|
||||
'Unexpected self-reference: a value should not have itself as a source',
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: sourceMetadata.place.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const childNodes = buildTreeNode(
|
||||
childId,
|
||||
context,
|
||||
|
||||
@@ -16,23 +16,18 @@ import {
|
||||
IdentifierId,
|
||||
isSetStateType,
|
||||
isUseEffectHookType,
|
||||
isUseEffectEventType,
|
||||
isUseInsertionEffectHookType,
|
||||
isUseLayoutEffectHookType,
|
||||
isUseRefType,
|
||||
isRefValueType,
|
||||
Place,
|
||||
Effect,
|
||||
BlockId,
|
||||
} from '../HIR';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionValueOperand,
|
||||
} from '../HIR/visitors';
|
||||
import {createControlDominators} from '../Inference/ControlDominators';
|
||||
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
import {Result} from '../Utils/Result';
|
||||
import {assertExhaustive, Iterable_some} from '../Utils/utils';
|
||||
import {Iterable_some} from '../Utils/utils';
|
||||
|
||||
/**
|
||||
* Validates against calling setState in the body of an effect (useEffect and friends),
|
||||
@@ -99,20 +94,7 @@ export function validateNoSetStateInEffects(
|
||||
instr.value.kind === 'MethodCall'
|
||||
? instr.value.receiver
|
||||
: instr.value.callee;
|
||||
|
||||
if (isUseEffectEventType(callee.identifier)) {
|
||||
const arg = instr.value.args[0];
|
||||
if (arg !== undefined && arg.kind === 'Identifier') {
|
||||
const setState = setStateFunctions.get(arg.identifier.id);
|
||||
if (setState !== undefined) {
|
||||
/**
|
||||
* This effect event function calls setState synchonously,
|
||||
* treat it as a setState function for transitive tracking
|
||||
*/
|
||||
setStateFunctions.set(instr.lvalue.identifier.id, setState);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
if (
|
||||
isUseEffectHookType(callee.identifier) ||
|
||||
isUseLayoutEffectHookType(callee.identifier) ||
|
||||
isUseInsertionEffectHookType(callee.identifier)
|
||||
@@ -121,58 +103,26 @@ export function validateNoSetStateInEffects(
|
||||
if (arg !== undefined && arg.kind === 'Identifier') {
|
||||
const setState = setStateFunctions.get(arg.identifier.id);
|
||||
if (setState !== undefined) {
|
||||
const enableVerbose =
|
||||
env.config.enableVerboseNoSetStateInEffect;
|
||||
if (enableVerbose) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.EffectSetState,
|
||||
reason:
|
||||
'Calling setState synchronously within an effect can trigger cascading renders',
|
||||
description:
|
||||
'Effects are intended to synchronize state between React and external systems. ' +
|
||||
'Calling setState synchronously causes cascading renders that hurt performance.\n\n' +
|
||||
'This pattern may indicate one of several issues:\n\n' +
|
||||
'**1. Non-local derived data**: If the value being set could be computed from props/state ' +
|
||||
'but requires data from a parent component, consider restructuring state ownership so the ' +
|
||||
'derivation can happen during render in the component that owns the relevant state.\n\n' +
|
||||
"**2. Derived event pattern**: If you're detecting when a prop changes (e.g., `isPlaying` " +
|
||||
'transitioning from false to true), this often indicates the parent should provide an event ' +
|
||||
'callback (like `onPlay`) instead of just the current state. Request access to the original event.\n\n' +
|
||||
"**3. Force update / external sync**: If you're forcing a re-render to sync with an external " +
|
||||
'data source (mutable values outside React), use `useSyncExternalStore` to properly subscribe ' +
|
||||
'to external state changes.\n\n' +
|
||||
'See: https://react.dev/learn/you-might-not-need-an-effect',
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: setState.loc,
|
||||
message:
|
||||
'Avoid calling setState() directly within an effect',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.EffectSetState,
|
||||
reason:
|
||||
'Calling setState synchronously within an effect can trigger cascading renders',
|
||||
description:
|
||||
'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' +
|
||||
'In general, the body of an effect should do one or both of the following:\n' +
|
||||
'* Update external systems with the latest state from React.\n' +
|
||||
'* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' +
|
||||
'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' +
|
||||
'(https://react.dev/learn/you-might-not-need-an-effect)',
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: setState.loc,
|
||||
message:
|
||||
'Avoid calling setState() directly within an effect',
|
||||
}),
|
||||
);
|
||||
}
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.EffectSetState,
|
||||
reason:
|
||||
'Calling setState synchronously within an effect can trigger cascading renders',
|
||||
description:
|
||||
'Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. ' +
|
||||
'In general, the body of an effect should do one or both of the following:\n' +
|
||||
'* Update external systems with the latest state from React.\n' +
|
||||
'* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\n' +
|
||||
'Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. ' +
|
||||
'(https://react.dev/learn/you-might-not-need-an-effect)',
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: setState.loc,
|
||||
message:
|
||||
'Avoid calling setState() directly within an effect',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,8 +140,6 @@ function getSetStateCall(
|
||||
setStateFunctions: Map<IdentifierId, Place>,
|
||||
env: Environment,
|
||||
): Place | null {
|
||||
const enableAllowSetStateFromRefsInEffects =
|
||||
env.config.enableAllowSetStateFromRefsInEffects;
|
||||
const refDerivedValues: Set<IdentifierId> = new Set();
|
||||
|
||||
const isDerivedFromRef = (place: Place): boolean => {
|
||||
@@ -202,38 +150,9 @@ function getSetStateCall(
|
||||
);
|
||||
};
|
||||
|
||||
const isRefControlledBlock: (id: BlockId) => boolean =
|
||||
enableAllowSetStateFromRefsInEffects
|
||||
? createControlDominators(fn, place => isDerivedFromRef(place))
|
||||
: (): boolean => false;
|
||||
|
||||
for (const [, block] of fn.body.blocks) {
|
||||
if (enableAllowSetStateFromRefsInEffects) {
|
||||
for (const phi of block.phis) {
|
||||
if (isDerivedFromRef(phi.place)) {
|
||||
continue;
|
||||
}
|
||||
let isPhiDerivedFromRef = false;
|
||||
for (const [, operand] of phi.operands) {
|
||||
if (isDerivedFromRef(operand)) {
|
||||
isPhiDerivedFromRef = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isPhiDerivedFromRef) {
|
||||
refDerivedValues.add(phi.place.identifier.id);
|
||||
} else {
|
||||
for (const [pred] of phi.operands) {
|
||||
if (isRefControlledBlock(pred)) {
|
||||
refDerivedValues.add(phi.place.identifier.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (enableAllowSetStateFromRefsInEffects) {
|
||||
if (env.config.enableAllowSetStateFromRefsInEffects) {
|
||||
const hasRefOperand = Iterable_some(
|
||||
eachInstructionValueOperand(instr.value),
|
||||
isDerivedFromRef,
|
||||
@@ -243,46 +162,6 @@ function getSetStateCall(
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
refDerivedValues.add(lvalue.identifier.id);
|
||||
}
|
||||
// Ref-derived values can also propagate through mutation
|
||||
for (const operand of eachInstructionValueOperand(instr.value)) {
|
||||
switch (operand.effect) {
|
||||
case Effect.Capture:
|
||||
case Effect.Store:
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.ConditionallyMutateIterator:
|
||||
case Effect.Mutate: {
|
||||
if (isMutable(instr, operand)) {
|
||||
refDerivedValues.add(operand.identifier.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Effect.Freeze:
|
||||
case Effect.Read: {
|
||||
// no-op
|
||||
break;
|
||||
}
|
||||
case Effect.Unknown: {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected unknown effect',
|
||||
description: null,
|
||||
details: [
|
||||
{
|
||||
kind: 'error',
|
||||
loc: operand.loc,
|
||||
message: null,
|
||||
},
|
||||
],
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
operand.effect,
|
||||
`Unexpected effect kind \`${operand.effect}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -324,7 +203,7 @@ function getSetStateCall(
|
||||
isSetStateType(callee.identifier) ||
|
||||
setStateFunctions.has(callee.identifier.id)
|
||||
) {
|
||||
if (enableAllowSetStateFromRefsInEffects) {
|
||||
if (env.config.enableAllowSetStateFromRefsInEffects) {
|
||||
const arg = instr.value.args.at(0);
|
||||
if (
|
||||
arg !== undefined &&
|
||||
@@ -337,8 +216,6 @@ function getSetStateCall(
|
||||
* be needed when initial layout measurements from refs need to be stored in state.
|
||||
*/
|
||||
return null;
|
||||
} else if (isRefControlledBlock(block.id)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -155,40 +155,20 @@ function validateNoSetStateInRenderImpl(
|
||||
}),
|
||||
);
|
||||
} else if (unconditionalBlocks.has(block.id)) {
|
||||
const enableUseKeyedState = fn.env.config.enableUseKeyedState;
|
||||
if (enableUseKeyedState) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.RenderSetState,
|
||||
reason: 'Cannot call setState during render',
|
||||
description:
|
||||
'Calling setState during render may trigger an infinite loop.\n' +
|
||||
'* To reset state when other state/props change, use `const [state, setState] = useKeyedState(initialState, key)` to reset `state` when `key` changes.\n' +
|
||||
'* To derive data from other state/props, compute the derived data during render without using state',
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: 'Found setState() in render',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.RenderSetState,
|
||||
reason: 'Cannot call setState during render',
|
||||
description:
|
||||
'Calling setState during render may trigger an infinite loop.\n' +
|
||||
'* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders\n' +
|
||||
'* To derive data from other state/props, compute the derived data during render without using state',
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: 'Found setState() in render',
|
||||
}),
|
||||
);
|
||||
}
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.RenderSetState,
|
||||
reason:
|
||||
'Calling setState during render may trigger an infinite loop',
|
||||
description:
|
||||
'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)',
|
||||
suggestions: null,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: callee.loc,
|
||||
message: 'Found setState() in render',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -242,7 +242,6 @@ function validateInferredDep(
|
||||
normalizedDep = {
|
||||
root: maybeNormalizedRoot.root,
|
||||
path: [...maybeNormalizedRoot.path, ...dep.path],
|
||||
loc: maybeNormalizedRoot.loc,
|
||||
};
|
||||
} else {
|
||||
CompilerError.invariant(dep.identifier.name?.kind === 'named', {
|
||||
@@ -268,10 +267,8 @@ function validateInferredDep(
|
||||
effect: Effect.Read,
|
||||
reactive: false,
|
||||
},
|
||||
constant: false,
|
||||
},
|
||||
path: [...dep.path],
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
for (const decl of declsWithinMemoBlock) {
|
||||
@@ -382,10 +379,8 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: storeTarget,
|
||||
constant: false,
|
||||
},
|
||||
path: [],
|
||||
loc: storeTarget.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -413,10 +408,8 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
root: {
|
||||
kind: 'NamedLocal',
|
||||
value: {...lvalue},
|
||||
constant: false,
|
||||
},
|
||||
path: [],
|
||||
loc: lvalue.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,7 @@ import {Result} from '../Utils/Result';
|
||||
|
||||
/**
|
||||
* Some common node types that are important for coverage tracking.
|
||||
* Based on istanbul-lib-instrument + some other common nodes we expect to be present in the generated AST.
|
||||
*
|
||||
* Note: For VariableDeclaration, VariableDeclarator, and Identifier, we enforce stricter validation
|
||||
* that requires both the source location AND node type to match in the generated AST. This ensures
|
||||
* that variable declarations maintain their structural integrity through compilation.
|
||||
* Based on istanbul-lib-instrument
|
||||
*/
|
||||
const IMPORTANT_INSTRUMENTED_TYPES = new Set([
|
||||
'ArrowFunctionExpression',
|
||||
@@ -58,14 +54,6 @@ const IMPORTANT_INSTRUMENTED_TYPES = new Set([
|
||||
'LabeledStatement',
|
||||
'ConditionalExpression',
|
||||
'LogicalExpression',
|
||||
|
||||
/**
|
||||
* Note: these aren't important for coverage tracking,
|
||||
* but we still want to track them to ensure we aren't regressing them when
|
||||
* we fix the source location tracking for other nodes.
|
||||
*/
|
||||
'VariableDeclaration',
|
||||
'Identifier',
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -126,13 +114,10 @@ export function validateSourceLocations(
|
||||
): Result<void, CompilerError> {
|
||||
const errors = new CompilerError();
|
||||
|
||||
/*
|
||||
* Step 1: Collect important locations from the original source
|
||||
* Note: Multiple node types can share the same location (e.g. VariableDeclarator and Identifier)
|
||||
*/
|
||||
// Step 1: Collect important locations from the original source
|
||||
const importantOriginalLocations = new Map<
|
||||
string,
|
||||
{loc: t.SourceLocation; nodeTypes: Set<string>}
|
||||
{loc: t.SourceLocation; nodeType: string}
|
||||
>();
|
||||
|
||||
func.traverse({
|
||||
@@ -152,31 +137,20 @@ export function validateSourceLocations(
|
||||
// Collect the location if it exists
|
||||
if (node.loc) {
|
||||
const key = locationKey(node.loc);
|
||||
const existing = importantOriginalLocations.get(key);
|
||||
if (existing) {
|
||||
existing.nodeTypes.add(node.type);
|
||||
} else {
|
||||
importantOriginalLocations.set(key, {
|
||||
loc: node.loc,
|
||||
nodeTypes: new Set([node.type]),
|
||||
});
|
||||
}
|
||||
importantOriginalLocations.set(key, {
|
||||
loc: node.loc,
|
||||
nodeType: node.type,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Step 2: Collect all locations from the generated AST with their node types
|
||||
const generatedLocations = new Map<string, Set<string>>();
|
||||
// Step 2: Collect all locations from the generated AST
|
||||
const generatedLocations = new Set<string>();
|
||||
|
||||
function collectGeneratedLocations(node: t.Node): void {
|
||||
if (node.loc) {
|
||||
const key = locationKey(node.loc);
|
||||
const nodeTypes = generatedLocations.get(key);
|
||||
if (nodeTypes) {
|
||||
nodeTypes.add(node.type);
|
||||
} else {
|
||||
generatedLocations.set(key, new Set([node.type]));
|
||||
}
|
||||
generatedLocations.add(locationKey(node.loc));
|
||||
}
|
||||
|
||||
// Use Babel's VISITOR_KEYS to traverse only actual node properties
|
||||
@@ -209,86 +183,22 @@ export function validateSourceLocations(
|
||||
collectGeneratedLocations(outlined.fn.body);
|
||||
}
|
||||
|
||||
/*
|
||||
* Step 3: Validate that all important locations are preserved
|
||||
* For certain node types, also validate that the node type matches
|
||||
*/
|
||||
const strictNodeTypes = new Set([
|
||||
'VariableDeclaration',
|
||||
'VariableDeclarator',
|
||||
'Identifier',
|
||||
]);
|
||||
|
||||
const reportMissingLocation = (
|
||||
loc: t.SourceLocation,
|
||||
nodeType: string,
|
||||
): void => {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Important source location missing in generated code',
|
||||
description:
|
||||
`Source location for ${nodeType} is missing in the generated output. This can cause coverage instrumentation ` +
|
||||
`to fail to track this code properly, resulting in inaccurate coverage reports.`,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: null,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const reportWrongNodeType = (
|
||||
loc: t.SourceLocation,
|
||||
expectedType: string,
|
||||
actualTypes: Set<string>,
|
||||
): void => {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Todo,
|
||||
reason:
|
||||
'Important source location has wrong node type in generated code',
|
||||
description:
|
||||
`Source location for ${expectedType} exists in the generated output but with wrong node type(s): ${Array.from(actualTypes).join(', ')}. ` +
|
||||
`This can cause coverage instrumentation to fail to track this code properly, resulting in inaccurate coverage reports.`,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: null,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
for (const [key, {loc, nodeTypes}] of importantOriginalLocations) {
|
||||
const generatedNodeTypes = generatedLocations.get(key);
|
||||
|
||||
if (!generatedNodeTypes) {
|
||||
// Location is completely missing
|
||||
reportMissingLocation(loc, Array.from(nodeTypes).join(', '));
|
||||
} else {
|
||||
// Location exists, check each node type
|
||||
for (const nodeType of nodeTypes) {
|
||||
if (
|
||||
strictNodeTypes.has(nodeType) &&
|
||||
!generatedNodeTypes.has(nodeType)
|
||||
) {
|
||||
/*
|
||||
* For strict node types, the specific node type must be present
|
||||
* Check if any generated node type is also an important original node type
|
||||
*/
|
||||
const hasValidNodeType = Array.from(generatedNodeTypes).some(
|
||||
genType => nodeTypes.has(genType),
|
||||
);
|
||||
|
||||
if (hasValidNodeType) {
|
||||
// At least one generated node type is valid (also in original), so this is just missing
|
||||
reportMissingLocation(loc, nodeType);
|
||||
} else {
|
||||
// None of the generated node types are in original - this is wrong node type
|
||||
reportWrongNodeType(loc, nodeType, generatedNodeTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Step 3: Validate that all important locations are preserved
|
||||
for (const [key, {loc, nodeType}] of importantOriginalLocations) {
|
||||
if (!generatedLocations.has(key)) {
|
||||
errors.pushDiagnostic(
|
||||
CompilerDiagnostic.create({
|
||||
category: ErrorCategory.Todo,
|
||||
reason: 'Important source location missing in generated code',
|
||||
description:
|
||||
`Source location for ${nodeType} is missing in the generated output. This can cause coverage instrumentation ` +
|
||||
`to fail to track this code properly, resulting in inaccurate coverage reports.`,
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc,
|
||||
message: null,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,10 @@ function Component() {
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const a = makeObject_Primitives();
|
||||
|
||||
const x = [];
|
||||
x.push(a);
|
||||
|
||||
mutate(x);
|
||||
t0 = [x, a];
|
||||
$[0] = t0;
|
||||
|
||||
@@ -33,6 +33,7 @@ function Component() {
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const x = [];
|
||||
x.push(a);
|
||||
|
||||
t1 = [x, a];
|
||||
$[1] = t1;
|
||||
} else {
|
||||
|
||||
@@ -85,10 +85,14 @@ function Component(t0) {
|
||||
let t1;
|
||||
if ($[0] !== prop) {
|
||||
const obj = shallowCopy(prop);
|
||||
|
||||
const aliasedObj = identity(obj);
|
||||
|
||||
const getId = () => obj.id;
|
||||
|
||||
mutate(aliasedObj);
|
||||
setPropertyByKey(aliasedObj, "id", prop.id + 1);
|
||||
|
||||
t1 = <Stringify getId={getId} shouldInvokeFns={true} />;
|
||||
$[0] = prop;
|
||||
$[1] = t1;
|
||||
|
||||
@@ -181,9 +181,12 @@ function Component(t0) {
|
||||
if ($[0] !== prop) {
|
||||
const obj = shallowCopy(prop);
|
||||
const aliasedObj = identity(obj);
|
||||
|
||||
const id = [obj.id];
|
||||
|
||||
mutate(aliasedObj);
|
||||
setPropertyByKey(aliasedObj, "id", prop.id + 1);
|
||||
|
||||
t1 = <Stringify id={id} />;
|
||||
$[0] = prop;
|
||||
$[1] = t1;
|
||||
|
||||
@@ -54,6 +54,7 @@ function Foo(t0) {
|
||||
let t1;
|
||||
if ($[0] !== cond1 || $[1] !== cond2) {
|
||||
const arr = makeArray({ a: 2 }, 2, []);
|
||||
|
||||
t1 = cond1 ? (
|
||||
<>
|
||||
<div>{identity("foo")}</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
const someGlobal = {value: 0};
|
||||
@@ -33,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false
|
||||
import { useMemo } from "react";
|
||||
|
||||
const someGlobal = { value: 0 };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enablePreserveExistingMemoizationGuarantees:false @validateExhaustiveMemoizationDependencies:false
|
||||
// @enablePreserveExistingMemoizationGuarantees:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
const someGlobal = {value: 0};
|
||||
|
||||
@@ -49,6 +49,7 @@ function Component() {
|
||||
ref.current = "";
|
||||
}
|
||||
};
|
||||
|
||||
t0 = () => {
|
||||
setRef();
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@ function Component() {
|
||||
ref.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
t0 = () => {
|
||||
setRef();
|
||||
};
|
||||
|
||||
@@ -74,6 +74,7 @@ function Component() {
|
||||
console.log(ref.current.value);
|
||||
}
|
||||
};
|
||||
|
||||
t0 = (
|
||||
<>
|
||||
<input ref={ref} />
|
||||
|
||||
@@ -36,6 +36,7 @@ function useArrayOfRef() {
|
||||
const callback = (value) => {
|
||||
ref.current = value;
|
||||
};
|
||||
|
||||
t0 = [callback];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
|
||||
@@ -35,6 +35,7 @@ function Component(props) {
|
||||
const arr = [...bar(props)];
|
||||
return arr.at(x);
|
||||
};
|
||||
|
||||
t1 = fn();
|
||||
$[2] = props;
|
||||
$[3] = x;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
|
||||
import { useMemo } from "react";
|
||||
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @validateExhaustiveMemoizationDependencies:false
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ function useBar(t0) {
|
||||
if ($[0] !== arg) {
|
||||
const s = new Set([1, 5, 4]);
|
||||
const mutableIterator = s.values();
|
||||
|
||||
t1 = [arg, ...mutableIterator];
|
||||
$[0] = arg;
|
||||
$[1] = t1;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
@@ -31,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies:false
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @validateExhaustiveMemoizationDependencies:false
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -28,6 +28,7 @@ function Component(props) {
|
||||
const a = [];
|
||||
const b = {};
|
||||
foo(a, b);
|
||||
|
||||
foo(b);
|
||||
t0 = <div a={a} b={b} />;
|
||||
$[0] = t0;
|
||||
|
||||
@@ -45,6 +45,7 @@ function useKeyCommand() {
|
||||
const nextPosition = direction === "left" ? addOne(position) : position;
|
||||
currentPosition.current = nextPosition;
|
||||
};
|
||||
|
||||
const moveLeft = { handler: handleKey("left") };
|
||||
const moveRight = { handler: handleKey("right") };
|
||||
t0 = [moveLeft, moveRight];
|
||||
|
||||
@@ -45,6 +45,7 @@ function Component(t0) {
|
||||
z.a = 2;
|
||||
mutate(y.b);
|
||||
};
|
||||
|
||||
x();
|
||||
t1 = [y, z];
|
||||
$[0] = a;
|
||||
|
||||
@@ -29,6 +29,7 @@ function MyComponentName(props) {
|
||||
const x = {};
|
||||
foo(x, props.a);
|
||||
foo(x, props.b);
|
||||
|
||||
y = [];
|
||||
y.push(x);
|
||||
$[0] = props.a;
|
||||
|
||||
@@ -34,6 +34,7 @@ function useTest() {
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
let w = {};
|
||||
|
||||
const t1 = (w = 42);
|
||||
const t2 = w;
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ function useTest() {
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const w = {};
|
||||
|
||||
const t1 = (w.x = 42);
|
||||
const t2 = w.x;
|
||||
|
||||
|
||||
@@ -44,9 +44,11 @@ function ComponentA(props) {
|
||||
if (b) {
|
||||
a.push(props.p0);
|
||||
}
|
||||
|
||||
if (props.p1) {
|
||||
b.push(props.p2);
|
||||
}
|
||||
|
||||
t0 = <Foo a={a} b={b} />;
|
||||
$[0] = props.p0;
|
||||
$[1] = props.p1;
|
||||
@@ -67,9 +69,11 @@ function ComponentB(props) {
|
||||
if (mayMutate(b)) {
|
||||
a.push(props.p0);
|
||||
}
|
||||
|
||||
if (props.p1) {
|
||||
b.push(props.p2);
|
||||
}
|
||||
|
||||
t0 = <Foo a={a} b={b} />;
|
||||
$[0] = props.p0;
|
||||
$[1] = props.p1;
|
||||
|
||||
@@ -28,6 +28,7 @@ function Component(props) {
|
||||
const a = [];
|
||||
const b = {};
|
||||
new Foo(a, b);
|
||||
|
||||
new Foo(b);
|
||||
t0 = <div a={a} b={b} />;
|
||||
$[0] = t0;
|
||||
|
||||
@@ -11,7 +11,7 @@ function Component(props) {
|
||||
|
||||
Component = useMemo(() => {
|
||||
return Component;
|
||||
}, [Component]);
|
||||
});
|
||||
|
||||
return <Component {...props} />;
|
||||
}
|
||||
@@ -36,7 +36,6 @@ function Component(props) {
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
Component = Stringify;
|
||||
|
||||
Component;
|
||||
Component = Component;
|
||||
$[0] = Component;
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,7 @@ function Component(props) {
|
||||
|
||||
Component = useMemo(() => {
|
||||
return Component;
|
||||
}, [Component]);
|
||||
});
|
||||
|
||||
return <Component {...props} />;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ function Component(props) {
|
||||
const callback = () => {
|
||||
console.log(x);
|
||||
};
|
||||
|
||||
x = {};
|
||||
t0 = <Stringify callback={callback} shouldInvokeFns={true} />;
|
||||
$[0] = t0;
|
||||
|
||||
@@ -75,6 +75,7 @@ function Component(props) {
|
||||
let t0;
|
||||
if ($[0] !== post) {
|
||||
const allUrls = [];
|
||||
|
||||
const { media: t1, comments: t2, urls: t3 } = post;
|
||||
const media = t1 === undefined ? null : t1;
|
||||
let t4;
|
||||
@@ -101,6 +102,7 @@ function Component(props) {
|
||||
if (!comments.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(comments.length);
|
||||
};
|
||||
$[6] = comments.length;
|
||||
@@ -109,6 +111,7 @@ function Component(props) {
|
||||
t6 = $[7];
|
||||
}
|
||||
const onClick = t6;
|
||||
|
||||
allUrls.push(...urls);
|
||||
t0 = <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
|
||||
$[0] = post;
|
||||
|
||||
@@ -53,6 +53,7 @@ function Component(props) {
|
||||
let t0;
|
||||
if ($[0] !== post) {
|
||||
const allUrls = [];
|
||||
|
||||
const { media, comments, urls } = post;
|
||||
let t1;
|
||||
if ($[2] !== comments.length) {
|
||||
@@ -60,6 +61,7 @@ function Component(props) {
|
||||
if (!comments.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(comments.length);
|
||||
};
|
||||
$[2] = comments.length;
|
||||
@@ -68,6 +70,7 @@ function Component(props) {
|
||||
t1 = $[3];
|
||||
}
|
||||
const onClick = t1;
|
||||
|
||||
allUrls.push(...urls);
|
||||
t0 = <Media media={media} onClick={onClick} />;
|
||||
$[0] = post;
|
||||
|
||||
@@ -57,6 +57,7 @@ function Component(t0) {
|
||||
let y;
|
||||
if ($[0] !== a || $[1] !== b || $[2] !== c) {
|
||||
x = [];
|
||||
|
||||
if (a) {
|
||||
let t1;
|
||||
if ($[5] !== b) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({value, enabled}) {
|
||||
@@ -29,21 +29,43 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ value, enabled }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(6);
|
||||
const { value, enabled } = t0;
|
||||
const [localValue, setLocalValue] = useState("");
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== enabled || $[1] !== value) {
|
||||
t1 = () => {
|
||||
if (enabled) {
|
||||
setLocalValue(value);
|
||||
} else {
|
||||
setLocalValue("disabled");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (enabled) {
|
||||
setLocalValue(value);
|
||||
} else {
|
||||
setLocalValue("disabled");
|
||||
}
|
||||
}, [value, enabled]);
|
||||
|
||||
return <div>{localValue}</div>;
|
||||
t2 = [value, enabled];
|
||||
$[0] = enabled;
|
||||
$[1] = value;
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
t2 = $[3];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[4] !== localValue) {
|
||||
t3 = <div>{localValue}</div>;
|
||||
$[4] = localValue;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -56,8 +78,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":6,"index":263},"end":{"line":9,"column":19,"index":276},"filename":"derived-state-conditionally-in-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":16,"column":1,"index":397},"filename":"derived-state-conditionally-in-effect.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":6,"index":244},"end":{"line":9,"column":19,"index":257},"filename":"derived-state-conditionally-in-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":16,"column":1,"index":378},"filename":"derived-state-conditionally-in-effect.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({value, enabled}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
export default function Component({input = 'empty'}) {
|
||||
@@ -26,18 +26,38 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Component({ input = "empty" }) {
|
||||
export default function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { input: t1 } = t0;
|
||||
const input = t1 === undefined ? "empty" : t1;
|
||||
const [currInput, setCurrInput] = useState(input);
|
||||
const localConst = "local const";
|
||||
|
||||
useEffect(() => {
|
||||
setCurrInput(input + localConst);
|
||||
}, [input, localConst]);
|
||||
|
||||
return <div>{currInput}</div>;
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[0] !== input) {
|
||||
t2 = () => {
|
||||
setCurrInput(input + "local const");
|
||||
};
|
||||
t3 = [input, "local const"];
|
||||
$[0] = input;
|
||||
$[1] = t2;
|
||||
$[2] = t3;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
t3 = $[2];
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
let t4;
|
||||
if ($[3] !== currInput) {
|
||||
t4 = <div>{currInput}</div>;
|
||||
$[3] = currInput;
|
||||
$[4] = t4;
|
||||
} else {
|
||||
t4 = $[4];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -50,8 +70,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [input]\n\nData Flow Tree:\n└── input (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":295},"end":{"line":9,"column":16,"index":307},"filename":"derived-state-from-default-props.ts","identifierName":"setCurrInput"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":141},"end":{"line":13,"column":1,"index":391},"filename":"derived-state-from-default-props.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [input]\n\nData Flow Tree:\n└── input (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":276},"end":{"line":9,"column":16,"index":288},"filename":"derived-state-from-default-props.ts","identifierName":"setCurrInput"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":122},"end":{"line":13,"column":1,"index":372},"filename":"derived-state-from-default-props.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
export default function Component({input = 'empty'}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
@@ -23,20 +23,45 @@ function Component({shouldChange}) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ shouldChange }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { shouldChange } = t0;
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldChange) {
|
||||
setCount(count + 1);
|
||||
}
|
||||
}, [count]);
|
||||
|
||||
return <div>{count}</div>;
|
||||
let t1;
|
||||
if ($[0] !== count || $[1] !== shouldChange) {
|
||||
t1 = () => {
|
||||
if (shouldChange) {
|
||||
setCount(count + 1);
|
||||
}
|
||||
};
|
||||
$[0] = count;
|
||||
$[1] = shouldChange;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== count) {
|
||||
t2 = [count];
|
||||
$[3] = count;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[5] !== count) {
|
||||
t3 = <div>{count}</div>;
|
||||
$[5] = count;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -44,8 +69,8 @@ function Component({ shouldChange }) {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [count]\n\nData Flow Tree:\n└── count (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":10,"column":6,"index":256},"end":{"line":10,"column":14,"index":264},"filename":"derived-state-from-local-state-in-effect.ts","identifierName":"setCount"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":127},"end":{"line":15,"column":1,"index":329},"filename":"derived-state-from-local-state-in-effect.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [count]\n\nData Flow Tree:\n└── count (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":10,"column":6,"index":237},"end":{"line":10,"column":14,"index":245},"filename":"derived-state-from-local-state-in-effect.ts","identifierName":"setCount"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":108},"end":{"line":15,"column":1,"index":310},"filename":"derived-state-from-local-state-in-effect.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({firstName}) {
|
||||
@@ -33,25 +33,68 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ firstName }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(12);
|
||||
const { firstName } = t0;
|
||||
const [lastName, setLastName] = useState("Doe");
|
||||
const [fullName, setFullName] = useState("John");
|
||||
|
||||
const middleName = "D.";
|
||||
|
||||
useEffect(() => {
|
||||
setFullName(firstName + " " + middleName + " " + lastName);
|
||||
}, [firstName, middleName, lastName]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input value={lastName} onChange={(e) => setLastName(e.target.value)} />
|
||||
<div>{fullName}</div>
|
||||
</div>
|
||||
);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== firstName || $[1] !== lastName) {
|
||||
t1 = () => {
|
||||
setFullName(firstName + " " + "D." + " " + lastName);
|
||||
};
|
||||
t2 = [firstName, "D.", lastName];
|
||||
$[0] = firstName;
|
||||
$[1] = lastName;
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
t2 = $[3];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = (e) => setLastName(e.target.value);
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
let t4;
|
||||
if ($[5] !== lastName) {
|
||||
t4 = <input value={lastName} onChange={t3} />;
|
||||
$[5] = lastName;
|
||||
$[6] = t4;
|
||||
} else {
|
||||
t4 = $[6];
|
||||
}
|
||||
let t5;
|
||||
if ($[7] !== fullName) {
|
||||
t5 = <div>{fullName}</div>;
|
||||
$[7] = fullName;
|
||||
$[8] = t5;
|
||||
} else {
|
||||
t5 = $[8];
|
||||
}
|
||||
let t6;
|
||||
if ($[9] !== t4 || $[10] !== t5) {
|
||||
t6 = (
|
||||
<div>
|
||||
{t4}
|
||||
{t5}
|
||||
</div>
|
||||
);
|
||||
$[9] = t4;
|
||||
$[10] = t5;
|
||||
$[11] = t6;
|
||||
} else {
|
||||
t6 = $[11];
|
||||
}
|
||||
return t6;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -64,8 +107,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [firstName]\nState: [lastName]\n\nData Flow Tree:\n├── firstName (Prop)\n└── lastName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":316},"end":{"line":11,"column":15,"index":327},"filename":"derived-state-from-prop-local-state-and-component-scope.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":20,"column":1,"index":561},"filename":"derived-state-from-prop-local-state-and-component-scope.ts"},"fnName":"Component","memoSlots":12,"memoBlocks":5,"memoValues":6,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [firstName]\nState: [lastName]\n\nData Flow Tree:\n├── firstName (Prop)\n└── lastName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":297},"end":{"line":11,"column":15,"index":308},"filename":"derived-state-from-prop-local-state-and-component-scope.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":20,"column":1,"index":542},"filename":"derived-state-from-prop-local-state-and-component-scope.ts"},"fnName":"Component","memoSlots":12,"memoBlocks":5,"memoValues":6,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({firstName}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({initialName}) {
|
||||
@@ -29,21 +29,48 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ initialName }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(6);
|
||||
const { initialName } = t0;
|
||||
const [name, setName] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setName(initialName);
|
||||
}, [initialName]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</div>
|
||||
);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== initialName) {
|
||||
t1 = () => {
|
||||
setName(initialName);
|
||||
};
|
||||
t2 = [initialName];
|
||||
$[0] = initialName;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = (e) => setName(e.target.value);
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
let t4;
|
||||
if ($[4] !== name) {
|
||||
t4 = (
|
||||
<div>
|
||||
<input value={name} onChange={t3} />
|
||||
</div>
|
||||
);
|
||||
$[4] = name;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -56,7 +83,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":16,"column":1,"index":378},"filename":"derived-state-from-prop-setter-call-outside-effect-no-error.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":3,"memoValues":4,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":16,"column":1,"index":359},"filename":"derived-state-from-prop-setter-call-outside-effect-no-error.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":3,"memoValues":4,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({initialName}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp
|
||||
|
||||
function Component({value}) {
|
||||
const [checked, setChecked] = useState('');
|
||||
@@ -19,16 +19,36 @@ function Component({value}) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp
|
||||
|
||||
function Component({ value }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { value } = t0;
|
||||
const [checked, setChecked] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setChecked(value === "" ? [] : value.split(","));
|
||||
}, [value]);
|
||||
|
||||
return <div>{checked}</div>;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== value) {
|
||||
t1 = () => {
|
||||
setChecked(value === "" ? [] : value.split(","));
|
||||
};
|
||||
t2 = [value];
|
||||
$[0] = value;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] !== checked) {
|
||||
t3 = <div>{checked}</div>;
|
||||
$[3] = checked;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp
|
||||
|
||||
function Component({value}) {
|
||||
const [checked, setChecked] = useState('');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function MockComponent({onSet}) {
|
||||
@@ -28,20 +28,50 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function MockComponent({ onSet }) {
|
||||
return <div onClick={() => onSet("clicked")}>Mock Component</div>;
|
||||
function MockComponent(t0) {
|
||||
const $ = _c(2);
|
||||
const { onSet } = t0;
|
||||
let t1;
|
||||
if ($[0] !== onSet) {
|
||||
t1 = <div onClick={() => onSet("clicked")}>Mock Component</div>;
|
||||
$[0] = onSet;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
function Component({ propValue }) {
|
||||
const [value, setValue] = useState(null);
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
}, [propValue]);
|
||||
|
||||
return <MockComponent onSet={setValue} />;
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const { propValue } = t0;
|
||||
const [, setValue] = useState(null);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== propValue) {
|
||||
t1 = () => {
|
||||
setValue(propValue);
|
||||
};
|
||||
t2 = [propValue];
|
||||
$[0] = propValue;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = <MockComponent onSet={setValue} />;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -54,8 +84,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":6,"column":1,"index":230},"filename":"derived-state-from-prop-setter-used-outside-effect-no-error.ts"},"fnName":"MockComponent","memoSlots":2,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":8,"column":0,"index":232},"end":{"line":15,"column":1,"index":421},"filename":"derived-state-from-prop-setter-used-outside-effect-no-error.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":6,"column":1,"index":211},"filename":"derived-state-from-prop-setter-used-outside-effect-no-error.ts"},"fnName":"MockComponent","memoSlots":2,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":8,"column":0,"index":213},"end":{"line":15,"column":1,"index":402},"filename":"derived-state-from-prop-setter-used-outside-effect-no-error.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function MockComponent({onSet}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({value}) {
|
||||
@@ -26,18 +26,38 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ value }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { value } = t0;
|
||||
const [localValue, setLocalValue] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setLocalValue(value);
|
||||
document.title = `Value: ${value}`;
|
||||
}, [value]);
|
||||
|
||||
return <div>{localValue}</div>;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== value) {
|
||||
t1 = () => {
|
||||
setLocalValue(value);
|
||||
document.title = `Value: ${value}`;
|
||||
};
|
||||
t2 = [value];
|
||||
$[0] = value;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] !== localValue) {
|
||||
t3 = <div>{localValue}</div>;
|
||||
$[3] = localValue;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -50,8 +70,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":8,"column":4,"index":233},"end":{"line":8,"column":17,"index":246},"filename":"derived-state-from-prop-with-side-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":13,"column":1,"index":346},"filename":"derived-state-from-prop-with-side-effect.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [value]\n\nData Flow Tree:\n└── value (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":8,"column":4,"index":214},"end":{"line":8,"column":17,"index":227},"filename":"derived-state-from-prop-with-side-effect.ts","identifierName":"setLocalValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":13,"column":1,"index":327},"filename":"derived-state-from-prop-with-side-effect.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({value}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState, useRef} from 'react';
|
||||
|
||||
export default function Component({test}) {
|
||||
@@ -27,19 +27,39 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
export default function Component({ test }) {
|
||||
export default function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { test } = t0;
|
||||
const [local, setLocal] = useState("");
|
||||
|
||||
const myRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setLocal(myRef.current + test);
|
||||
}, [test]);
|
||||
|
||||
return <>{local}</>;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== test) {
|
||||
t1 = () => {
|
||||
setLocal(myRef.current + test);
|
||||
};
|
||||
t2 = [test];
|
||||
$[0] = test;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] !== local) {
|
||||
t3 = <>{local}</>;
|
||||
$[3] = local;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -52,7 +72,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":149},"end":{"line":14,"column":1,"index":347},"filename":"derived-state-from-ref-and-state-no-error.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":130},"end":{"line":14,"column":1,"index":328},"filename":"derived-state-from-ref-and-state-no-error.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState, useRef} from 'react';
|
||||
|
||||
export default function Component({test}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue}) {
|
||||
@@ -30,22 +30,48 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ propValue }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(6);
|
||||
const { propValue } = t0;
|
||||
const [value, setValue] = useState(null);
|
||||
|
||||
function localFunction() {
|
||||
console.log("local function");
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = function localFunction() {
|
||||
console.log("local function");
|
||||
};
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
localFunction();
|
||||
}, [propValue]);
|
||||
|
||||
return <div>{value}</div>;
|
||||
const localFunction = t1;
|
||||
let t2;
|
||||
let t3;
|
||||
if ($[1] !== propValue) {
|
||||
t2 = () => {
|
||||
setValue(propValue);
|
||||
localFunction();
|
||||
};
|
||||
t3 = [propValue];
|
||||
$[1] = propValue;
|
||||
$[2] = t2;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
t3 = $[3];
|
||||
}
|
||||
useEffect(t2, t3);
|
||||
let t4;
|
||||
if ($[4] !== value) {
|
||||
t4 = <div>{value}</div>;
|
||||
$[4] = value;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -58,8 +84,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [propValue]\n\nData Flow Tree:\n└── propValue (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":12,"column":4,"index":298},"end":{"line":12,"column":12,"index":306},"filename":"effect-contains-local-function-call.ts","identifierName":"setValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":17,"column":1,"index":390},"filename":"effect-contains-local-function-call.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":3,"memoValues":4,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [propValue]\n\nData Flow Tree:\n└── propValue (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":12,"column":4,"index":279},"end":{"line":12,"column":12,"index":287},"filename":"effect-contains-local-function-call.ts","identifierName":"setValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":17,"column":1,"index":371},"filename":"effect-contains-local-function-call.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":3,"memoValues":4,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue, onChange}) {
|
||||
@@ -25,17 +25,43 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ propValue, onChange }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { propValue, onChange } = t0;
|
||||
const [value, setValue] = useState(null);
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
onChange();
|
||||
}, [propValue]);
|
||||
|
||||
return <div>{value}</div>;
|
||||
let t1;
|
||||
if ($[0] !== onChange || $[1] !== propValue) {
|
||||
t1 = () => {
|
||||
setValue(propValue);
|
||||
onChange();
|
||||
};
|
||||
$[0] = onChange;
|
||||
$[1] = propValue;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== propValue) {
|
||||
t2 = [propValue];
|
||||
$[3] = propValue;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[5] !== value) {
|
||||
t3 = <div>{value}</div>;
|
||||
$[5] = value;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -48,8 +74,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":12,"column":1,"index":325},"filename":"effect-contains-prop-function-call-no-error.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":16,"column":41,"index":421},"end":{"line":16,"column":49,"index":429},"filename":"effect-contains-prop-function-call-no-error.ts"},"fnName":null,"memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":12,"column":1,"index":306},"filename":"effect-contains-prop-function-call-no-error.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":16,"column":41,"index":402},"end":{"line":16,"column":49,"index":410},"filename":"effect-contains-prop-function-call-no-error.ts"},"fnName":null,"memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue, onChange}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
function Component({prop}) {
|
||||
const [s, setS] = useState(0);
|
||||
@@ -18,15 +18,36 @@ function Component({prop}) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
function Component({ prop }) {
|
||||
const [s, setS] = useState(0);
|
||||
useEffect(() => {
|
||||
setS(prop);
|
||||
}, [prop, setS]);
|
||||
|
||||
return <div>{prop}</div>;
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { prop } = t0;
|
||||
const [, setS] = useState(0);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== prop) {
|
||||
t1 = () => {
|
||||
setS(prop);
|
||||
};
|
||||
t2 = [prop, setS];
|
||||
$[0] = prop;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] !== prop) {
|
||||
t3 = <div>{prop}</div>;
|
||||
$[3] = prop;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -34,8 +55,8 @@ function Component({ prop }) {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [prop]\n\nData Flow Tree:\n└── prop (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":6,"column":4,"index":169},"end":{"line":6,"column":8,"index":173},"filename":"effect-used-in-dep-array-still-errors.ts","identifierName":"setS"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":83},"end":{"line":10,"column":1,"index":231},"filename":"effect-used-in-dep-array-still-errors.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [prop]\n\nData Flow Tree:\n└── prop (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":6,"column":4,"index":150},"end":{"line":6,"column":8,"index":154},"filename":"effect-used-in-dep-array-still-errors.ts","identifierName":"setS"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":64},"end":{"line":10,"column":1,"index":212},"filename":"effect-used-in-dep-array-still-errors.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
function Component({prop}) {
|
||||
const [s, setS] = useState(0);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
@@ -29,26 +29,39 @@ function Component(file: File) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component(file: File) {
|
||||
function Component(file) {
|
||||
const $ = _c(5);
|
||||
const [imageUrl, setImageUrl] = useState(null);
|
||||
|
||||
/*
|
||||
* Cleaning up the variable or a source of the variable used to setState
|
||||
* inside the effect communicates that we always need to clean up something
|
||||
* which is a valid use case for useEffect. In which case we want to
|
||||
* avoid an throwing
|
||||
*/
|
||||
useEffect(() => {
|
||||
const imageUrlPrepared = URL.createObjectURL(file);
|
||||
setImageUrl(imageUrlPrepared);
|
||||
return () => URL.revokeObjectURL(imageUrlPrepared);
|
||||
}, [file]);
|
||||
|
||||
return <Image src={imageUrl} xstyle={styles.imageSizeLimits} />;
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== file) {
|
||||
t0 = () => {
|
||||
const imageUrlPrepared = URL.createObjectURL(file);
|
||||
setImageUrl(imageUrlPrepared);
|
||||
return () => URL.revokeObjectURL(imageUrlPrepared);
|
||||
};
|
||||
t1 = [file];
|
||||
$[0] = file;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
if ($[3] !== imageUrl) {
|
||||
t2 = <Image src={imageUrl} xstyle={styles.imageSizeLimits} />;
|
||||
$[3] = imageUrl;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -56,7 +69,7 @@ function Component(file: File) {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":127},"end":{"line":21,"column":1,"index":719},"filename":"effect-with-cleanup-function-depending-on-derived-computation-value.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":108},"end":{"line":21,"column":1,"index":700},"filename":"effect-with-cleanup-function-depending-on-derived-computation-value.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue}) {
|
||||
@@ -25,17 +25,38 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component({ propValue }) {
|
||||
function Component(t0) {
|
||||
const $ = _c(5);
|
||||
const { propValue } = t0;
|
||||
const [value, setValue] = useState(null);
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
globalCall();
|
||||
}, [propValue]);
|
||||
|
||||
return <div>{value}</div>;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== propValue) {
|
||||
t1 = () => {
|
||||
setValue(propValue);
|
||||
globalCall();
|
||||
};
|
||||
t2 = [propValue];
|
||||
$[0] = propValue;
|
||||
$[1] = t1;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
t2 = $[2];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[3] !== value) {
|
||||
t3 = <div>{value}</div>;
|
||||
$[3] = value;
|
||||
$[4] = t3;
|
||||
} else {
|
||||
t3 = $[4];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -48,7 +69,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":12,"column":1,"index":317},"filename":"effect-with-global-function-call-no-error.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":12,"column":1,"index":298},"filename":"effect-with-global-function-call-no-error.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue}) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly
|
||||
|
||||
function Component({setParentState, prop}) {
|
||||
useEffect(() => {
|
||||
@@ -17,14 +17,40 @@ function Component({setParentState, prop}) {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly
|
||||
|
||||
function Component({ setParentState, prop }) {
|
||||
useEffect(() => {
|
||||
setParentState(prop);
|
||||
}, [prop]);
|
||||
|
||||
return <div>{prop}</div>;
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { setParentState, prop } = t0;
|
||||
let t1;
|
||||
if ($[0] !== prop || $[1] !== setParentState) {
|
||||
t1 = () => {
|
||||
setParentState(prop);
|
||||
};
|
||||
$[0] = prop;
|
||||
$[1] = setParentState;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
let t2;
|
||||
if ($[3] !== prop) {
|
||||
t2 = [prop];
|
||||
$[3] = prop;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
useEffect(t1, t2);
|
||||
let t3;
|
||||
if ($[5] !== prop) {
|
||||
t3 = <div>{prop}</div>;
|
||||
$[5] = prop;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -32,7 +58,7 @@ function Component({ setParentState, prop }) {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":124},"end":{"line":9,"column":1,"index":259},"filename":"from-props-setstate-in-effect-no-error.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":105},"end":{"line":9,"column":1,"index":240},"filename":"from-props-setstate-in-effect-no-error.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly
|
||||
|
||||
function Component({setParentState, prop}) {
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
|
||||
function Component() {
|
||||
const [foo, setFoo] = useState({});
|
||||
const [bar, setBar] = useState(new Set());
|
||||
|
||||
/*
|
||||
* isChanged is considered context of the effect's function expression,
|
||||
* if we don't bail out of effect mutation derivation tracking, isChanged
|
||||
* will inherit the sources of the effect's function expression.
|
||||
*
|
||||
* This is innacurate and with the multiple passes ends up causing an infinite loop.
|
||||
*/
|
||||
useEffect(() => {
|
||||
let isChanged = false;
|
||||
|
||||
const newData = foo.map(val => {
|
||||
bar.someMethod(val);
|
||||
isChanged = true;
|
||||
});
|
||||
|
||||
if (isChanged) {
|
||||
setFoo(newData);
|
||||
}
|
||||
}, [foo, bar]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{foo}, {bar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
|
||||
function Component() {
|
||||
const [foo, setFoo] = useState({});
|
||||
const [bar, setBar] = useState(new Set());
|
||||
|
||||
/*
|
||||
* isChanged is considered context of the effect's function expression,
|
||||
* if we don't bail out of effect mutation derivation tracking, isChanged
|
||||
* will inherit the sources of the effect's function expression.
|
||||
*
|
||||
* This is innacurate and with the multiple passes ends up causing an infinite loop.
|
||||
*/
|
||||
useEffect(() => {
|
||||
let isChanged = false;
|
||||
|
||||
const newData = foo.map((val) => {
|
||||
bar.someMethod(val);
|
||||
isChanged = true;
|
||||
});
|
||||
|
||||
if (isChanged) {
|
||||
setFoo(newData);
|
||||
}
|
||||
}, [foo, bar]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{foo}, {bar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [foo, bar]\n\nData Flow Tree:\n└── newData\n ├── foo (State)\n └── bar (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":23,"column":6,"index":682},"end":{"line":23,"column":12,"index":688},"filename":"function-expression-mutation-edge-case.ts","identifierName":"setFoo"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":83},"end":{"line":32,"column":1,"index":781},"filename":"function-expression-mutation-edge-case.ts"},"fnName":"Component","memoSlots":9,"memoBlocks":4,"memoValues":5,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -1,32 +0,0 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
|
||||
function Component() {
|
||||
const [foo, setFoo] = useState({});
|
||||
const [bar, setBar] = useState(new Set());
|
||||
|
||||
/*
|
||||
* isChanged is considered context of the effect's function expression,
|
||||
* if we don't bail out of effect mutation derivation tracking, isChanged
|
||||
* will inherit the sources of the effect's function expression.
|
||||
*
|
||||
* This is innacurate and with the multiple passes ends up causing an infinite loop.
|
||||
*/
|
||||
useEffect(() => {
|
||||
let isChanged = false;
|
||||
|
||||
const newData = foo.map(val => {
|
||||
bar.someMethod(val);
|
||||
isChanged = true;
|
||||
});
|
||||
|
||||
if (isChanged) {
|
||||
setFoo(newData);
|
||||
}
|
||||
}, [foo, bar]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{foo}, {bar}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component() {
|
||||
@@ -28,20 +28,38 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
function Component() {
|
||||
const [firstName, setFirstName] = useState("Taylor");
|
||||
const lastName = "Swift";
|
||||
const $ = _c(5);
|
||||
const [firstName] = useState("Taylor");
|
||||
|
||||
// 🔴 Avoid: redundant state and unnecessary Effect
|
||||
const [fullName, setFullName] = useState("");
|
||||
useEffect(() => {
|
||||
setFullName(firstName + " " + lastName);
|
||||
}, [firstName, lastName]);
|
||||
|
||||
return <div>{fullName}</div>;
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== firstName) {
|
||||
t0 = () => {
|
||||
setFullName(firstName + " " + "Swift");
|
||||
};
|
||||
t1 = [firstName, "Swift"];
|
||||
$[0] = firstName;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
if ($[3] !== fullName) {
|
||||
t2 = <div>{fullName}</div>;
|
||||
$[3] = fullName;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -54,8 +72,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [firstName]\n\nData Flow Tree:\n└── firstName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":360},"end":{"line":11,"column":15,"index":371},"filename":"invalid-derived-computation-in-effect.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":126},"end":{"line":15,"column":1,"index":464},"filename":"invalid-derived-computation-in-effect.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [firstName]\n\nData Flow Tree:\n└── firstName (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":11,"column":4,"index":341},"end":{"line":11,"column":15,"index":352},"filename":"invalid-derived-computation-in-effect.ts","identifierName":"setFullName"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":107},"end":{"line":15,"column":1,"index":445},"filename":"invalid-derived-computation-in-effect.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
export default function Component(props) {
|
||||
@@ -26,18 +26,39 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects_exp @loggerTestOnly @outputMode:"lint"
|
||||
import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @loggerTestOnly
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Component(props) {
|
||||
const $ = _c(7);
|
||||
const [displayValue, setDisplayValue] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const computed = props.prefix + props.value + props.suffix;
|
||||
setDisplayValue(computed);
|
||||
}, [props.prefix, props.value, props.suffix]);
|
||||
|
||||
return <div>{displayValue}</div>;
|
||||
let t0;
|
||||
let t1;
|
||||
if ($[0] !== props.prefix || $[1] !== props.suffix || $[2] !== props.value) {
|
||||
t0 = () => {
|
||||
const computed = props.prefix + props.value + props.suffix;
|
||||
setDisplayValue(computed);
|
||||
};
|
||||
t1 = [props.prefix, props.value, props.suffix];
|
||||
$[0] = props.prefix;
|
||||
$[1] = props.suffix;
|
||||
$[2] = props.value;
|
||||
$[3] = t0;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
t1 = $[4];
|
||||
}
|
||||
useEffect(t0, t1);
|
||||
let t2;
|
||||
if ($[5] !== displayValue) {
|
||||
t2 = <div>{displayValue}</div>;
|
||||
$[5] = displayValue;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -50,8 +71,8 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [props]\n\nData Flow Tree:\n└── computed\n └── props (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":314},"end":{"line":9,"column":19,"index":329},"filename":"invalid-derived-state-from-computed-props.ts","identifierName":"setDisplayValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":141},"end":{"line":13,"column":1,"index":428},"filename":"invalid-derived-state-from-computed-props.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nProps: [props]\n\nData Flow Tree:\n└── computed\n └── props (Prop)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":9,"column":4,"index":295},"end":{"line":9,"column":19,"index":310},"filename":"invalid-derived-state-from-computed-props.ts","identifierName":"setDisplayValue"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
|
||||
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":15,"index":122},"end":{"line":13,"column":1,"index":409},"filename":"invalid-derived-state-from-computed-props.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user