From ae3190c5e929ec3b899734e2a6aface7cd4e42d0 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Fri, 7 Feb 2025 10:54:44 +0000 Subject: [PATCH 01/73] React DevTools 6.1.0 -> 6.1.1 (#32326) Full list of changes: * DevTools: refactor NativeStyleEditor, don't use custom cache implementation ([hoxyq](https://github.com/hoxyq) in [#32298](https://github.com/facebook/react/pull/32298)) * fix[react-devtools-fusebox]: add extension globals to build ([hoxyq](https://github.com/hoxyq) in [#32297](https://github.com/facebook/react/pull/32297)) * DevTools: fix host component filter option title ([hoxyq](https://github.com/hoxyq) in [#32296](https://github.com/facebook/react/pull/32296)) * chore[DevTools]: make clipboardWrite optional for chromium ([hoxyq](https://github.com/hoxyq) in [#32262](https://github.com/facebook/react/pull/32262)) * DevTools: support useEffectEvent and forward-fix experimental prefix support ([hoxyq](https://github.com/hoxyq) in [#32106](https://github.com/facebook/react/pull/32106)) --- packages/react-devtools-core/package.json | 2 +- packages/react-devtools-extensions/chrome/manifest.json | 4 ++-- packages/react-devtools-extensions/edge/manifest.json | 4 ++-- packages/react-devtools-extensions/firefox/manifest.json | 2 +- packages/react-devtools-inline/package.json | 2 +- packages/react-devtools-timeline/package.json | 2 +- packages/react-devtools/CHANGELOG.md | 9 +++++++++ packages/react-devtools/package.json | 4 ++-- 8 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json index 88951de463c96..6794f631772be 100644 --- a/packages/react-devtools-core/package.json +++ b/packages/react-devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-core", - "version": "6.1.0", + "version": "6.1.1", "description": "Use react-devtools outside of the browser", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-extensions/chrome/manifest.json b/packages/react-devtools-extensions/chrome/manifest.json index 4b2d810f60411..5cf6397bc75ef 100644 --- a/packages/react-devtools-extensions/chrome/manifest.json +++ b/packages/react-devtools-extensions/chrome/manifest.json @@ -2,8 +2,8 @@ "manifest_version": 3, "name": "React Developer Tools", "description": "Adds React debugging tools to the Chrome Developer Tools.", - "version": "6.1.0", - "version_name": "6.1.0", + "version": "6.1.1", + "version_name": "6.1.1", "minimum_chrome_version": "102", "icons": { "16": "icons/16-production.png", diff --git a/packages/react-devtools-extensions/edge/manifest.json b/packages/react-devtools-extensions/edge/manifest.json index 37a76be2f24bc..512dd888f7d94 100644 --- a/packages/react-devtools-extensions/edge/manifest.json +++ b/packages/react-devtools-extensions/edge/manifest.json @@ -2,8 +2,8 @@ "manifest_version": 3, "name": "React Developer Tools", "description": "Adds React debugging tools to the Microsoft Edge Developer Tools.", - "version": "6.1.0", - "version_name": "6.1.0", + "version": "6.1.1", + "version_name": "6.1.1", "minimum_chrome_version": "102", "icons": { "16": "icons/16-production.png", diff --git a/packages/react-devtools-extensions/firefox/manifest.json b/packages/react-devtools-extensions/firefox/manifest.json index d053921ebeecc..22dee6a10a42d 100644 --- a/packages/react-devtools-extensions/firefox/manifest.json +++ b/packages/react-devtools-extensions/firefox/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "React Developer Tools", "description": "Adds React debugging tools to the Firefox Developer Tools.", - "version": "6.1.0", + "version": "6.1.1", "browser_specific_settings": { "gecko": { "id": "@react-devtools", diff --git a/packages/react-devtools-inline/package.json b/packages/react-devtools-inline/package.json index 86cdc5758b3e9..4a484dedbeb5e 100644 --- a/packages/react-devtools-inline/package.json +++ b/packages/react-devtools-inline/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-inline", - "version": "6.1.0", + "version": "6.1.1", "description": "Embed react-devtools within a website", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-timeline/package.json b/packages/react-devtools-timeline/package.json index 5519ab339da78..9dbf65b931587 100644 --- a/packages/react-devtools-timeline/package.json +++ b/packages/react-devtools-timeline/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "react-devtools-timeline", - "version": "6.1.0", + "version": "6.1.1", "license": "MIT", "dependencies": { "@elg/speedscope": "1.9.0-a6f84db", diff --git a/packages/react-devtools/CHANGELOG.md b/packages/react-devtools/CHANGELOG.md index 4e6109a3186fd..b1a4fd9a801e0 100644 --- a/packages/react-devtools/CHANGELOG.md +++ b/packages/react-devtools/CHANGELOG.md @@ -4,6 +4,15 @@ --- +### 6.1.1 +February 7, 2025 + +* DevTools: refactor NativeStyleEditor, don't use custom cache implementation ([hoxyq](https://github.com/hoxyq) in [#32298](https://github.com/facebook/react/pull/32298)) +* DevTools: fix host component filter option title ([hoxyq](https://github.com/hoxyq) in [#32296](https://github.com/facebook/react/pull/32296)) +* chore[DevTools]: make clipboardWrite optional for chromium ([hoxyq](https://github.com/hoxyq) in [#32262](https://github.com/facebook/react/pull/32262)) + +--- + ### 6.1.0 January 16, 2025 diff --git a/packages/react-devtools/package.json b/packages/react-devtools/package.json index bc6af6588e9ec..a384b168166aa 100644 --- a/packages/react-devtools/package.json +++ b/packages/react-devtools/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools", - "version": "6.1.0", + "version": "6.1.1", "description": "Use react-devtools outside of the browser", "license": "MIT", "repository": { @@ -26,7 +26,7 @@ "electron": "^23.1.2", "internal-ip": "^6.2.0", "minimist": "^1.2.3", - "react-devtools-core": "6.1.0", + "react-devtools-core": "6.1.1", "update-notifier": "^2.1.0" } } From 44c3d3d665761bba86eb5c143a6eafc0ebf73263 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Fri, 7 Feb 2025 11:07:46 +0000 Subject: [PATCH 02/73] fix[react-devtools-standalone]: define missing globals (#32327) Same as what we did for `react-devtools-fusebox` in https://github.com/facebook/react/pull/32297. --- packages/react-devtools-core/webpack.standalone.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-devtools-core/webpack.standalone.js b/packages/react-devtools-core/webpack.standalone.js index 778c1fa85c627..8caadec10b070 100644 --- a/packages/react-devtools-core/webpack.standalone.js +++ b/packages/react-devtools-core/webpack.standalone.js @@ -88,6 +88,9 @@ module.exports = { __PROFILE__: false, __TEST__: NODE_ENV === 'test', __IS_NATIVE__: true, + __IS_FIREFOX__: false, + __IS_CHROME__: false, + __IS_EDGE__: false, 'process.env.DEVTOOLS_PACKAGE': `"react-devtools-core"`, 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`, 'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null, From 8759c5c8d6aef34df576827215ff7ebaeafc79ea Mon Sep 17 00:00:00 2001 From: Jack Pope Date: Fri, 7 Feb 2025 11:12:29 -0500 Subject: [PATCH 03/73] Ship enableFabricCompleteRootInCommitPhase (#32318) --- .../src/ReactFiberConfigFabric.js | 14 +++----------- packages/shared/ReactFeatureFlags.js | 5 ----- .../forks/ReactFeatureFlags.native-fb-dynamic.js | 1 - .../shared/forks/ReactFeatureFlags.native-fb.js | 1 - .../shared/forks/ReactFeatureFlags.native-oss.js | 1 - .../forks/ReactFeatureFlags.test-renderer.js | 1 - .../ReactFeatureFlags.test-renderer.native-fb.js | 1 - .../forks/ReactFeatureFlags.test-renderer.www.js | 1 - packages/shared/forks/ReactFeatureFlags.www.js | 1 - 9 files changed, 3 insertions(+), 23 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 59bd296138e0b..d602ab05efa81 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -57,10 +57,7 @@ import { getInspectorDataForInstance, } from './ReactNativeFiberInspector'; -import { - enableFabricCompleteRootInCommitPhase, - passChildrenWhenCloningPersistedNodes, -} from 'shared/ReactFeatureFlags'; +import {passChildrenWhenCloningPersistedNodes} from 'shared/ReactFeatureFlags'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import type {ReactContext} from 'shared/ReactTypes'; @@ -505,19 +502,14 @@ export function finalizeContainerChildren( container: Container, newChildren: ChildSet, ): void { - if (!enableFabricCompleteRootInCommitPhase) { - completeRoot(container.containerTag, newChildren); - } + // Noop - children will be finalized in replaceContainerChildren } export function replaceContainerChildren( container: Container, newChildren: ChildSet, ): void { - // Noop - children will be replaced in finalizeContainerChildren - if (enableFabricCompleteRootInCommitPhase) { - completeRoot(container.containerTag, newChildren); - } + completeRoot(container.containerTag, newChildren); } export {getClosestInstanceFromNode as getInstanceFromNode}; diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index a94f3402de4f8..290d5ba159d68 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -92,11 +92,6 @@ export const enableHalt = __EXPERIMENTAL__; export const enableViewTransition = __EXPERIMENTAL__; -/** - * Switches the Fabric API from doing layout in commit work instead of complete work. - */ -export const enableFabricCompleteRootInCommitPhase = false; - /** * Switches Fiber creation to a simple object instead of a constructor. */ diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js index cff57d26ecd95..6aaefdfa336fe 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js @@ -23,7 +23,6 @@ export const enableHiddenSubtreeInsertionEffectCleanup = __VARIANT__; export const enablePersistedModeClonedFlag = __VARIANT__; export const enableShallowPropDiffing = __VARIANT__; export const passChildrenWhenCloningPersistedNodes = __VARIANT__; -export const enableFabricCompleteRootInCommitPhase = __VARIANT__; export const enableSiblingPrerendering = __VARIANT__; export const enableUseResourceEffectHook = __VARIANT__; export const enableOwnerStacks = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 31b3333966410..878d344a42a41 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -20,7 +20,6 @@ const dynamicFlags: DynamicExportsType = (dynamicFlagsUntyped: any); // the exports object every time a flag is read. export const { alwaysThrottleRetries, - enableFabricCompleteRootInCommitPhase, enableHiddenSubtreeInsertionEffectCleanup, enableObjectFiber, enablePersistedModeClonedFlag, diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 84307ce62746c..a3202c0b80049 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -30,7 +30,6 @@ export const enableAsyncIterableChildren = false; export const enableCPUSuspense = false; export const enableCreateEventHandleAPI = false; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; -export const enableFabricCompleteRootInCommitPhase = false; export const enableMoveBefore = true; export const enableFizzExternalRuntime = true; export const enableHalt = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 90725f666e1a1..6bc085f540b23 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -36,7 +36,6 @@ export const enableUseEffectEventHook = false; export const favorSafetyOverHydrationPerf = true; export const enableLegacyFBSupport = false; export const enableMoveBefore = false; -export const enableFabricCompleteRootInCommitPhase = false; export const enableHiddenSubtreeInsertionEffectCleanup = false; export const enableHydrationLaneScheduling = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index b4df6f248f9fd..d62a59122f82c 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -61,7 +61,6 @@ export const renameElementSymbol = false; export const retryLaneExpirationMs = 5000; export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; -export const enableFabricCompleteRootInCommitPhase = false; export const enableSiblingPrerendering = true; export const enableUseResourceEffectHook = true; export const enableHydrationLaneScheduling = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index a09e479f1732b..301c01a018090 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -39,7 +39,6 @@ export const favorSafetyOverHydrationPerf = true; export const enableLegacyFBSupport = false; export const enableMoveBefore = false; export const enableRenderableContext = false; -export const enableFabricCompleteRootInCommitPhase = false; export const enableHiddenSubtreeInsertionEffectCleanup = true; export const enableRetryLaneExpiration = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index c84b89847fa0c..dcb77f9d84b5e 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -48,7 +48,6 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = __PROFILE__; export const enableProfilerNestedUpdatePhase = __PROFILE__; export const enableUpdaterTracking = __PROFILE__; -export const enableFabricCompleteRootInCommitPhase = false; export const enableSuspenseAvoidThisFallback = true; From 76e44c29110f12caff8302da624076b68547b8ef Mon Sep 17 00:00:00 2001 From: inottn Date: Sat, 8 Feb 2025 04:48:03 +0800 Subject: [PATCH 04/73] [compiler] Improve error messages for unhandled terminal and instruction kinds (#32324) ## Summary Improve the error message, as the value is currently an object instead of a string, which results in it being converted to '[object Object]'. ## How did you test this change? Already tested locally. --- .../src/ReactiveScopes/PrintReactiveFunction.ts | 5 ++++- .../src/TypeInference/InferTypes.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts index b5aa44ead095d..259fc06486888 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction.ts @@ -394,7 +394,10 @@ function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void { break; } default: - assertExhaustive(terminal, `Unhandled terminal ${terminal}`); + assertExhaustive( + terminal, + `Unhandled terminal kind \`${(terminal as any).kind}\``, + ); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts index 0270723c3e863..44bf539e847bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts @@ -420,7 +420,10 @@ function* generateInstructionTypes( break; } default: - assertExhaustive(value, `Unhandled instruction value kind: ${value}`); + assertExhaustive( + value, + `Unhandled instruction value kind: ${(value as any).kind}`, + ); } } From 989b0cccc215e5c4692552b0cc01d23938dcf99b Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 7 Feb 2025 16:04:46 -0500 Subject: [PATCH 05/73] [compiler] Add simple walltime measurement (#32331) Adds a new Timing logger event to the compiler which currently only records the walltime of running the compiler from the time the babel plugin's Program visitor enters to the time it exits. To enable, run the compiler with `ENABLE_REACT_COMPILER_TIMINGS=1 ...` or `export ENABLE_REACT_COMPILER_TIMINGS=1` to set it by default. --- .../src/Babel/BabelPlugin.ts | 93 +++++++++++++------ .../src/Entrypoint/Options.ts | 4 + 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index c648c66043980..aa49bda22b27e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -6,12 +6,15 @@ */ import type * as BabelCore from '@babel/core'; -import {compileProgram, parsePluginOptions} from '../Entrypoint'; +import {compileProgram, Logger, parsePluginOptions} from '../Entrypoint'; import { injectReanimatedFlag, pipelineUsesReanimatedPlugin, } from '../Entrypoint/Reanimated'; +const ENABLE_REACT_COMPILER_TIMINGS = + process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1'; + /* * The React Forget Babel Plugin * @param {*} _babel @@ -28,35 +31,65 @@ export default function BabelPluginReactCompiler( * prior to B, if A does not have a Program visitor and B does, B will run first. We always * want Forget to run true to source as possible. */ - Program(prog, pass): void { - let opts = parsePluginOptions(pass.opts); - const isDev = - (typeof __DEV__ !== 'undefined' && __DEV__ === true) || - process.env['NODE_ENV'] === 'development'; - if ( - opts.enableReanimatedCheck === true && - pipelineUsesReanimatedPlugin(pass.file.opts.plugins) - ) { - opts = injectReanimatedFlag(opts); - } - if ( - opts.environment.enableResetCacheOnSourceFileChanges !== false && - isDev - ) { - opts = { - ...opts, - environment: { - ...opts.environment, - enableResetCacheOnSourceFileChanges: true, - }, - }; - } - compileProgram(prog, { - opts, - filename: pass.filename ?? null, - comments: pass.file.ast.comments ?? [], - code: pass.file.code, - }); + Program: { + enter(prog, pass): void { + const filename = pass.filename ?? 'unknown'; + if (ENABLE_REACT_COMPILER_TIMINGS === true) { + performance.mark(`${filename}:start`, { + detail: 'BabelPlugin:Program:start', + }); + } + let opts = parsePluginOptions(pass.opts); + const isDev = + (typeof __DEV__ !== 'undefined' && __DEV__ === true) || + process.env['NODE_ENV'] === 'development'; + if ( + opts.enableReanimatedCheck === true && + pipelineUsesReanimatedPlugin(pass.file.opts.plugins) + ) { + opts = injectReanimatedFlag(opts); + } + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { + opts = { + ...opts, + environment: { + ...opts.environment, + enableResetCacheOnSourceFileChanges: true, + }, + }; + } + compileProgram(prog, { + opts, + filename: pass.filename ?? null, + comments: pass.file.ast.comments ?? [], + code: pass.file.code, + }); + if (ENABLE_REACT_COMPILER_TIMINGS === true) { + performance.mark(`${filename}:end`, { + detail: 'BabelPlugin:Program:end', + }); + } + }, + exit(_, pass): void { + if (ENABLE_REACT_COMPILER_TIMINGS === true) { + const filename = pass.filename ?? 'unknown'; + const measurement = performance.measure(filename, { + start: `${filename}:start`, + end: `${filename}:end`, + detail: 'BabelPlugin:Program', + }); + if ('logger' in pass.opts && pass.opts.logger != null) { + const logger: Logger = pass.opts.logger as Logger; + logger.logEvent(filename, { + kind: 'Timing', + measurement, + }); + } + } + }, }, }, }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index fb951d25c5398..35c2c4134eb44 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -206,6 +206,10 @@ export type LoggerEvent = kind: 'PipelineError'; fnLoc: t.SourceLocation | null; data: string; + } + | { + kind: 'Timing'; + measurement: PerformanceMeasure; }; export type Logger = { From bc78de3a521f65f756579fee0996ec5c81f3af73 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 7 Feb 2025 16:34:26 -0500 Subject: [PATCH 06/73] [ci] Use 'opened' event for discord notifications (#32332) We don't need to wait for it to be labeled now that we have the shared maintainer check workflow. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32332). * #32333 * __->__ #32332 --- .github/workflows/compiler_discord_notify.yml | 4 ++-- .github/workflows/runtime_discord_notify.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compiler_discord_notify.yml b/.github/workflows/compiler_discord_notify.yml index 1ab9c1b1aff5b..da85190dd2127 100644 --- a/.github/workflows/compiler_discord_notify.yml +++ b/.github/workflows/compiler_discord_notify.yml @@ -2,7 +2,7 @@ name: (Compiler) Discord Notify on: pull_request_target: - types: [labeled] + types: [opened] paths: - compiler/** - .github/workflows/compiler_**.yml @@ -14,7 +14,7 @@ jobs: actor: ${{ github.event.pull_request.user.login }} notify: - if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' && github.event.label.name == 'React Core Team' }} + if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }} needs: check_maintainer runs-on: ubuntu-latest steps: diff --git a/.github/workflows/runtime_discord_notify.yml b/.github/workflows/runtime_discord_notify.yml index 59a1078b499e8..5a138ce71b13b 100644 --- a/.github/workflows/runtime_discord_notify.yml +++ b/.github/workflows/runtime_discord_notify.yml @@ -2,7 +2,7 @@ name: (Runtime) Discord Notify on: pull_request_target: - types: [labeled] + types: [opened] paths-ignore: - compiler/** - .github/workflows/compiler_**.yml @@ -14,7 +14,7 @@ jobs: actor: ${{ github.event.pull_request.user.login }} notify: - if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' && github.event.label.name == 'React Core Team' }} + if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }} needs: check_maintainer runs-on: ubuntu-latest steps: From 569c3b28ee968df58ff3c55be9a8efa2ee72fc82 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 7 Feb 2025 16:39:37 -0500 Subject: [PATCH 07/73] [ci] Combine sizebot jobs (#32333) There's no real reason to have 2 jobs for sizebot. It's more of a historical artifact from before the GH migration. Merging them should require one less worker needing to be provisioned and some of the extra overhead --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32333). * __->__ #32333 * #32332 --- .github/workflows/runtime_build_and_test.yml | 47 +++----------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index 9a592780b17c4..12e341a86fd96 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -585,9 +585,10 @@ jobs: RELEASE_CHANNEL: experimental # ----- SIZEBOT ----- - download_base_build_for_sizebot: - if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' }} - name: Download base build for sizebot + sizebot: + if: ${{ github.event_name == 'pull_request' && github.ref_name != 'main' && github.event.pull_request.base.ref == 'main' }} + name: Run sizebot + needs: [build_and_lint] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -603,16 +604,14 @@ jobs: id: node_modules with: path: "**/node_modules" - key: ${{ runner.arch }}-${{ runner.os }}-modules-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} - name: Ensure clean build directory run: rm -rf build - - run: yarn install --frozen-lockfile - run: yarn install --frozen-lockfile working-directory: scripts/release - name: Download artifacts for base revision run: | - git fetch origin main - GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse origin/main) + GH_TOKEN=${{ github.token }} scripts/release/download-experimental-build.js --commit=$(git rev-parse ${{ github.event.pull_request.base.sha }}) mv ./build ./base-build # TODO: The `download-experimental-build` script copies the npm # packages into the `node_modules` directory. This is a historical @@ -620,33 +619,8 @@ jobs: # don't exist. - name: Delete extraneous files run: rm -rf ./base-build/node_modules - - name: Display structure of base-build + - name: Display structure of base-build from origin/main run: ls -R base-build - - name: Archive base-build - uses: actions/upload-artifact@v4 - with: - name: base-build - path: base-build - - sizebot: - name: Run sizebot - needs: [build_and_lint, download_base_build_for_sizebot] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: yarn - cache-dependency-path: yarn.lock - - name: Restore cached node_modules - uses: actions/cache@v4 - id: node_modules - with: - path: "**/node_modules" - key: runtime-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} - name: Ensure clean build directory run: rm -rf build - run: yarn install --frozen-lockfile @@ -662,13 +636,6 @@ jobs: node ./scripts/print-warnings/print-warnings.js > build/__test_utils__/ReactAllWarnings.js - name: Display structure of build for PR run: ls -R build - - name: Restore archived base-build from origin/main - uses: actions/download-artifact@v4 - with: - name: base-build - path: base-build - - name: Display structure of base-build from origin/main - run: ls -R base-build - run: echo ${{ github.sha }} >> build/COMMIT_SHA - run: node ./scripts/tasks/danger - name: Archive sizebot results From 7588b6b291b17fb2130244115b07e9945a864626 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 7 Feb 2025 17:49:09 -0500 Subject: [PATCH 08/73] [ci] Disallow PRs against builds branch (#32335) Our internal build infra relies on a 1:1 mapping between `main` and the 2 build branches. Directly committing changes to those branches breaks that infra. Adds a simple workflow to leave a comment and decline the PR. --- .../shared_close_direct_sync_branch_prs.yml | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/shared_close_direct_sync_branch_prs.yml diff --git a/.github/workflows/shared_close_direct_sync_branch_prs.yml b/.github/workflows/shared_close_direct_sync_branch_prs.yml new file mode 100644 index 0000000000000..1f5dd5b1a465b --- /dev/null +++ b/.github/workflows/shared_close_direct_sync_branch_prs.yml @@ -0,0 +1,37 @@ +name: (Shared) Close Direct Sync Branch PRs + +on: + pull_request: + branches: + - builds/facebook-* + +env: + TZ: /usr/share/zoneinfo/America/Los_Angeles + # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 + +jobs: + close_pr: + runs-on: ubuntu-latest + steps: + - name: Close PR + uses: actions/github-script@v7 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const pullNumber = ${{ github.event.number }}; + + await github.rest.pulls.createReview({ + owner, + repo, + pull_number: pullNumber, + body: 'Do not land changes to `${{ github.event.pull_request.base.ref }}`. Please re-open your PR targeting `main` instead.', + event: 'REQUEST_CHANGES' + }); + await github.rest.pulls.update({ + owner, + repo, + pull_number: pullNumber, + state: 'closed' + }); From 062fb31155e42b6997a35b97180055814471620c Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 7 Feb 2025 18:01:53 -0500 Subject: [PATCH 09/73] [ci] Fix typo (#32337) Oops. --- .github/workflows/shared_close_direct_sync_branch_prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shared_close_direct_sync_branch_prs.yml b/.github/workflows/shared_close_direct_sync_branch_prs.yml index 1f5dd5b1a465b..7575c0e913c7e 100644 --- a/.github/workflows/shared_close_direct_sync_branch_prs.yml +++ b/.github/workflows/shared_close_direct_sync_branch_prs.yml @@ -3,7 +3,7 @@ name: (Shared) Close Direct Sync Branch PRs on: pull_request: branches: - - builds/facebook-* + - 'builds/facebook-**' env: TZ: /usr/share/zoneinfo/America/Los_Angeles From 594ea533d39f71fa16503c0e5d5e146e9278647e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Sun, 9 Feb 2025 15:01:28 -0500 Subject: [PATCH 10/73] Remove useActionState Hook export from experimental react-server condition (#32342) This Hook is not available in RSC environments. This is already the case in stable but not in experimental for some reason. Probably an oversight. --- packages/react/src/ReactServer.experimental.development.js | 2 -- packages/react/src/ReactServer.experimental.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/react/src/ReactServer.experimental.development.js b/packages/react/src/ReactServer.experimental.development.js index fc22dda4e7896..0176e0e94f7cc 100644 --- a/packages/react/src/ReactServer.experimental.development.js +++ b/packages/react/src/ReactServer.experimental.development.js @@ -30,7 +30,6 @@ import { useCallback, useDebugValue, useMemo, - useActionState, getCacheForType, } from './ReactHooks'; import {forwardRef} from './ReactForwardRef'; @@ -78,7 +77,6 @@ export { useCallback, useDebugValue, useMemo, - useActionState, version, // Experimental REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList, diff --git a/packages/react/src/ReactServer.experimental.js b/packages/react/src/ReactServer.experimental.js index 758c3d8b89ae0..c687bff7213b4 100644 --- a/packages/react/src/ReactServer.experimental.js +++ b/packages/react/src/ReactServer.experimental.js @@ -30,7 +30,6 @@ import { useCallback, useDebugValue, useMemo, - useActionState, getCacheForType, } from './ReactHooks'; import {forwardRef} from './ReactForwardRef'; @@ -77,7 +76,6 @@ export { useCallback, useDebugValue, useMemo, - useActionState, version, // Experimental REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList, From 93b58361d9c9632acdda76eb8a1a582d1ff9701a Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Sun, 9 Feb 2025 23:55:50 +0100 Subject: [PATCH 11/73] Trigger Discord notification when draft PR is set to "ready for review" (#32344) Follow-up for #32332. The Discord webhook seems to ignore draft PRs, which is a good thing. But when a draft PR is then later set to "ready for review" we do want to send another notification that should not be filtered out. --- .github/workflows/runtime_discord_notify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/runtime_discord_notify.yml b/.github/workflows/runtime_discord_notify.yml index 5a138ce71b13b..e41b1c56405a7 100644 --- a/.github/workflows/runtime_discord_notify.yml +++ b/.github/workflows/runtime_discord_notify.yml @@ -2,7 +2,7 @@ name: (Runtime) Discord Notify on: pull_request_target: - types: [opened] + types: [opened, ready_for_review] paths-ignore: - compiler/** - .github/workflows/compiler_**.yml From 3dd2c627707fea4f45fd8e5cc583036a72e3f77b Mon Sep 17 00:00:00 2001 From: lauren Date: Mon, 10 Feb 2025 14:08:44 -0500 Subject: [PATCH 12/73] [react-native] fix divergence in synced code (#32348) Alternative to #32334 --- packages/react-native-renderer/src/ReactNativeTypes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native-renderer/src/ReactNativeTypes.js b/packages/react-native-renderer/src/ReactNativeTypes.js index 55ae2cc1b0a0e..07030a6b7f6c7 100644 --- a/packages/react-native-renderer/src/ReactNativeTypes.js +++ b/packages/react-native-renderer/src/ReactNativeTypes.js @@ -10,6 +10,7 @@ */ import type {ElementRef, ElementType, MixedElement} from 'react'; +import {type PublicRootInstance} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; export type MeasureOnSuccessCallback = ( x: number, @@ -231,7 +232,6 @@ export opaque type Node = mixed; export opaque type InternalInstanceHandle = mixed; type PublicInstance = mixed; type PublicTextInstance = mixed; -export opaque type PublicRootInstance = mixed; export type ReactFabricType = { findHostInstance_DEPRECATED( @@ -261,6 +261,7 @@ export type ReactFabricType = { getPublicInstanceFromInternalInstanceHandle( internalInstanceHandle: InternalInstanceHandle, ): PublicInstance | PublicTextInstance | null, + getPublicInstanceFromRootTag(rootTag: number): PublicRootInstance | null, ... }; From cd90a4d8c0d5dbaa8ab61e839b112b1518d5058f Mon Sep 17 00:00:00 2001 From: lauren Date: Mon, 10 Feb 2025 15:46:47 -0500 Subject: [PATCH 13/73] [react-native] Suppress Flow nonstrict-import check in ReactNativeTypes (#32349) Summary: Unblock internal sync. Test Plan: Reviewers: Subscribers: Tasks: Tags: --- packages/react-native-renderer/src/ReactNativeTypes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native-renderer/src/ReactNativeTypes.js b/packages/react-native-renderer/src/ReactNativeTypes.js index 07030a6b7f6c7..8bce919d385e9 100644 --- a/packages/react-native-renderer/src/ReactNativeTypes.js +++ b/packages/react-native-renderer/src/ReactNativeTypes.js @@ -10,6 +10,7 @@ */ import type {ElementRef, ElementType, MixedElement} from 'react'; +// $FlowFixMe[nonstrict-import] TODO(@rubennorte) import {type PublicRootInstance} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; export type MeasureOnSuccessCallback = ( From 0a7dc1b1c714e74a1594c712d2317969e6421685 Mon Sep 17 00:00:00 2001 From: Brendan Abbott Date: Wed, 12 Feb 2025 04:14:43 +1000 Subject: [PATCH 14/73] [devtools] Introduce REACT_DEVTOOLS_PORT for the standalone react-devtools (#30767) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR attempts to make running the React DevTools a little friendlier in projects that are not completely React. At the moment, running the DevTools with `npx react-devtools` will default to the port to use the `PORT` env variable otherwise it'll fall back to `8097`. `PORT` is a common env variable, so we can get into this strange situation where the a Rails server (eg Puma) is using `PORT`, and then the React DevTools attempts to boot using the same `PORT`. This PR introduces a dedicated env variable, `REACT_DEVTOOLS_PORT` to assist in this scenario. ## How did you test this change? I'm using fish shell, so I did the following, please let me know if there's a better way: ```sh cd packages/react-devtools set -x PORT 1000 set -x REACT_DEVTOOLS_PORT 2000 node bin.js ``` We can see in the UI that it's listening on `2000`. Without this PR, it'd listen on `1000`: ![Screenshot 2024-08-21 at 10 45 42 AM](https://github.com/user-attachments/assets/a5c7590c-1b54-4ac8-9a8b-8eb66ff67cfb) --- packages/react-devtools/README.md | 2 +- packages/react-devtools/preload.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-devtools/README.md b/packages/react-devtools/README.md index 149ec97f8af57..8c436c1fb2b4d 100644 --- a/packages/react-devtools/README.md +++ b/packages/react-devtools/README.md @@ -87,7 +87,7 @@ This will ensure the developer tools are connected. **Don’t forget to remove i ## Advanced -By default DevTools listen to port `8097` on `localhost`. If you need to customize host, port, or other settings, see the `react-devtools-core` package instead. +By default DevTools listen to port `8097` on `localhost`. The port can be modified by setting the `REACT_DEVTOOLS_PORT` environment variable. If you need to further customize host, port, or other settings, see the `react-devtools-core` package instead. ## FAQ diff --git a/packages/react-devtools/preload.js b/packages/react-devtools/preload.js index 634cffc635a40..33a9e3d6dd46e 100644 --- a/packages/react-devtools/preload.js +++ b/packages/react-devtools/preload.js @@ -35,7 +35,7 @@ contextBridge.exposeInMainWorld('api', { } const host = process.env.HOST || 'localhost'; const protocol = useHttps ? 'https' : 'http'; - const port = +process.env.PORT || 8097; + const port = +process.env.REACT_DEVTOOLS_PORT || +process.env.PORT || 8097; return {options, useHttps, host, protocol, port}; }, }); From 899e3d1297ec15a5aa8d73e2f1bd478918090a12 Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 11 Feb 2025 13:52:25 -0500 Subject: [PATCH 15/73] [crud] Narrow resource type (#32203) Small refactor to the `resource` type to narrow it to an arbitrary object or void/null instead of the top type. This makes the overload on useEffect simpler since the return type of create is no longer widened to the top type when we merge their definitions. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32203). * #32206 * #32205 * #32204 * __->__ #32203 --- .../react-debug-tools/src/ReactDebugHooks.js | 6 +- .../src/ReactFiberCallUserSpace.js | 2 +- .../src/ReactFiberCommitEffects.js | 8 +- .../react-reconciler/src/ReactFiberHooks.js | 78 +++++++++---------- .../src/ReactInternalTypes.js | 6 +- packages/react/src/ReactHooks.js | 6 +- 6 files changed, 54 insertions(+), 52 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index f30489b7f655f..9c310723b2605 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -739,11 +739,11 @@ function useHostTransitionStatus(): TransitionStatus { } function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { nextHook(); hookLog.push({ diff --git a/packages/react-reconciler/src/ReactFiberCallUserSpace.js b/packages/react-reconciler/src/ReactFiberCallUserSpace.js index a1b86c7560a2a..dd7beecd93582 100644 --- a/packages/react-reconciler/src/ReactFiberCallUserSpace.js +++ b/packages/react-reconciler/src/ReactFiberCallUserSpace.js @@ -183,7 +183,7 @@ export const callComponentWillUnmountInDEV: ( const callCreate = { 'react-stack-bottom-frame': function ( effect: Effect, - ): (() => void) | mixed | void { + ): (() => void) | {...} | void | null { if (!enableUseResourceEffectHook) { if (effect.resourceKind != null) { if (__DEV__) { diff --git a/packages/react-reconciler/src/ReactFiberCommitEffects.js b/packages/react-reconciler/src/ReactFiberCommitEffects.js index 6873bdf9e7a63..a8e08721eed5f 100644 --- a/packages/react-reconciler/src/ReactFiberCommitEffects.js +++ b/packages/react-reconciler/src/ReactFiberCommitEffects.js @@ -274,6 +274,7 @@ export function commitHookEffectListMount( addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).'; + // $FlowFixMe (@poteto) this check is safe on arbitrary non-null/void objects } else if (typeof destroy.then === 'function') { addendum = '\n\nIt looks like you wrote ' + @@ -1036,10 +1037,10 @@ function safelyCallDestroy( function safelyCallDestroyWithResource( current: Fiber, nearestMountedAncestor: Fiber | null, - destroy: mixed => void, - resource: mixed, + destroy: ({...}) => void, + resource: {...}, ) { - const destroy_ = resource == null ? destroy : destroy.bind(null, resource); + const destroy_ = destroy.bind(null, resource); if (__DEV__) { runWithFiberInDEV( current, @@ -1050,6 +1051,7 @@ function safelyCallDestroyWithResource( ); } else { try { + // $FlowFixMe(incompatible-call) Already bound to resource destroy_(); } catch (error) { captureCommitPhaseError(current, nearestMountedAncestor, error); diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index f3a581a97476a..9d97710003317 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -205,8 +205,8 @@ export type Hook = { // the additional memory and we can follow up with performance // optimizations later. type EffectInstance = { - resource: mixed, - destroy: void | (() => void) | ((resource: mixed) => void), + resource: {...} | void | null, + destroy: void | (() => void) | ((resource: {...} | void | null) => void), }; export const ResourceEffectIdentityKind: 0 = 0; @@ -229,7 +229,7 @@ export type ResourceEffectIdentity = { resourceKind: typeof ResourceEffectIdentityKind, tag: HookFlags, inst: EffectInstance, - create: () => mixed, + create: () => {...} | void | null, deps: Array | void | null, next: Effect, }; @@ -237,7 +237,7 @@ export type ResourceEffectUpdate = { resourceKind: typeof ResourceEffectUpdateKind, tag: HookFlags, inst: EffectInstance, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, deps: Array | void | null, next: Effect, identity: ResourceEffectIdentity, @@ -2540,9 +2540,9 @@ function pushResourceEffect( identityTag: HookFlags, updateTag: HookFlags, inst: EffectInstance, - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, ): Effect { const effectIdentity: ResourceEffectIdentity = { @@ -2694,11 +2694,11 @@ function updateEffect( } function mountResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { if ( __DEV__ && @@ -2730,11 +2730,11 @@ function mountResourceEffect( function mountResourceEffectImpl( fiberFlags: Flags, hookFlags: HookFlags, - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { const hook = mountWorkInProgressHook(); currentlyRenderingFiber.flags |= fiberFlags; @@ -2752,11 +2752,11 @@ function mountResourceEffectImpl( } function updateResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { updateResourceEffectImpl( PassiveEffect, @@ -2772,11 +2772,11 @@ function updateResourceEffect( function updateResourceEffectImpl( fiberFlags: Flags, hookFlags: HookFlags, - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { const hook = updateWorkInProgressHook(); const effect: Effect = hook.memoizedState; @@ -4245,11 +4245,11 @@ if (__DEV__) { if (enableUseResourceEffectHook) { (HooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect = function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useResourceEffect'; mountHookTypesDev(); @@ -4433,11 +4433,11 @@ if (__DEV__) { if (enableUseResourceEffectHook) { (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useResourceEffect = function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useResourceEffect'; updateHookTypesDev(); @@ -4620,11 +4620,11 @@ if (__DEV__) { if (enableUseResourceEffectHook) { (HooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect = function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { currentHookNameInDev = 'useResourceEffect'; updateHookTypesDev(); @@ -4807,11 +4807,11 @@ if (__DEV__) { if (enableUseResourceEffectHook) { (HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect = function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { currentHookNameInDev = 'useResourceEffect'; updateHookTypesDev(); @@ -5019,11 +5019,11 @@ if (__DEV__) { if (enableUseResourceEffectHook) { (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect = function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useResourceEffect'; warnInvalidHookAccess(); @@ -5232,11 +5232,11 @@ if (__DEV__) { if (enableUseResourceEffectHook) { (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect = function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { currentHookNameInDev = 'useResourceEffect'; warnInvalidHookAccess(); @@ -5445,11 +5445,11 @@ if (__DEV__) { if (enableUseResourceEffectHook) { (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect = function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) { currentHookNameInDev = 'useResourceEffect'; warnInvalidHookAccess(); diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 0c5504cab468e..e31b7cefd1ed7 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -398,11 +398,11 @@ export type Dispatcher = { useEffectEvent?: ) => mixed>(callback: F) => F, // TODO: Non-nullable once `enableUseResourceEffectHook` is on everywhere. useResourceEffect?: ( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ) => void, useInsertionEffect( create: () => (() => void) | void, diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 32f49268880f0..ff45b5415c416 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -202,11 +202,11 @@ export function useEffectEvent) => mixed>( } export function useResourceEffect( - create: () => mixed, + create: () => {...} | void | null, createDeps: Array | void | null, - update: ((resource: mixed) => void) | void, + update: ((resource: {...} | void | null) => void) | void, updateDeps: Array | void | null, - destroy: ((resource: mixed) => void) | void, + destroy: ((resource: {...} | void | null) => void) | void, ): void { if (!enableUseResourceEffectHook) { throw new Error('Not implemented.'); From 0461c0d8a49730d1c8ebca2071d9bb7adfc8ac92 Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 11 Feb 2025 14:05:50 -0500 Subject: [PATCH 16/73] [crud] Rename useResourceEffect flag (#32204) Rename the flag in preparation for the overload. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32204). * #32206 * #32205 * __->__ #32204 --- .../ReactDOMServerIntegrationHooks-test.js | 2 +- .../src/ReactFiberCallUserSpace.js | 6 ++-- .../src/ReactFiberCommitEffects.js | 16 +++++----- .../react-reconciler/src/ReactFiberHooks.js | 24 +++++++------- .../src/ReactInternalTypes.js | 2 +- .../ReactHooksWithNoopRenderer-test.js | 32 +++++++++---------- packages/react-server/src/ReactFizzHooks.js | 4 +-- packages/react/src/ReactClient.js | 4 +-- packages/react/src/ReactHooks.js | 4 +-- packages/shared/ReactFeatureFlags.js | 2 +- .../ReactFeatureFlags.native-fb-dynamic.js | 2 +- .../forks/ReactFeatureFlags.native-fb.js | 2 +- .../forks/ReactFeatureFlags.native-oss.js | 2 +- .../forks/ReactFeatureFlags.test-renderer.js | 2 +- ...actFeatureFlags.test-renderer.native-fb.js | 2 +- .../ReactFeatureFlags.test-renderer.www.js | 2 +- .../forks/ReactFeatureFlags.www-dynamic.js | 2 +- .../shared/forks/ReactFeatureFlags.www.js | 2 +- 18 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js index b79e59ad004dd..bce830ddf0647 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js @@ -657,7 +657,7 @@ describe('ReactDOMServerHooks', () => { describe('useResourceEffect', () => { gate(flags => { - if (flags.enableUseResourceEffectHook) { + if (flags.enableUseEffectCRUDOverload) { const yields = []; itRenders( 'should ignore resource effects on the server', diff --git a/packages/react-reconciler/src/ReactFiberCallUserSpace.js b/packages/react-reconciler/src/ReactFiberCallUserSpace.js index dd7beecd93582..25ede645b5e7f 100644 --- a/packages/react-reconciler/src/ReactFiberCallUserSpace.js +++ b/packages/react-reconciler/src/ReactFiberCallUserSpace.js @@ -18,7 +18,7 @@ import { ResourceEffectIdentityKind, ResourceEffectUpdateKind, } from './ReactFiberHooks'; -import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags'; +import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags'; // These indirections exists so we can exclude its stack frame in DEV (and anything below it). // TODO: Consider marking the whole bundle instead of these boundaries. @@ -184,11 +184,11 @@ const callCreate = { 'react-stack-bottom-frame': function ( effect: Effect, ): (() => void) | {...} | void | null { - if (!enableUseResourceEffectHook) { + if (!enableUseEffectCRUDOverload) { if (effect.resourceKind != null) { if (__DEV__) { console.error( - 'Expected only SimpleEffects when enableUseResourceEffectHook is disabled, ' + + 'Expected only SimpleEffects when enableUseEffectCRUDOverload is disabled, ' + 'got %s', effect.resourceKind, ); diff --git a/packages/react-reconciler/src/ReactFiberCommitEffects.js b/packages/react-reconciler/src/ReactFiberCommitEffects.js index a8e08721eed5f..29e5de2817201 100644 --- a/packages/react-reconciler/src/ReactFiberCommitEffects.js +++ b/packages/react-reconciler/src/ReactFiberCommitEffects.js @@ -22,7 +22,7 @@ import { enableProfilerCommitHooks, enableProfilerNestedUpdatePhase, enableSchedulingProfiler, - enableUseResourceEffectHook, + enableUseEffectCRUDOverload, enableViewTransition, } from 'shared/ReactFeatureFlags'; import { @@ -160,7 +160,7 @@ export function commitHookEffectListMount( // Mount let destroy; - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { if (effect.resourceKind === ResourceEffectIdentityKind) { if (__DEV__) { effect.inst.resource = runWithFiberInDEV( @@ -200,7 +200,7 @@ export function commitHookEffectListMount( if ((flags & HookInsertion) !== NoHookEffect) { setIsRunningInsertionEffect(true); } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { if (effect.resourceKind == null) { destroy = runWithFiberInDEV( finishedWork, @@ -219,7 +219,7 @@ export function commitHookEffectListMount( setIsRunningInsertionEffect(false); } } else { - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { if (effect.resourceKind == null) { const create = effect.create; const inst = effect.inst; @@ -230,7 +230,7 @@ export function commitHookEffectListMount( if (effect.resourceKind != null) { if (__DEV__) { console.error( - 'Expected only SimpleEffects when enableUseResourceEffectHook is disabled, ' + + 'Expected only SimpleEffects when enableUseEffectCRUDOverload is disabled, ' + 'got %s', effect.resourceKind, ); @@ -262,7 +262,7 @@ export function commitHookEffectListMount( } else if ((effect.tag & HookInsertion) !== NoFlags) { hookName = 'useInsertionEffect'; } else if ( - enableUseResourceEffectHook && + enableUseEffectCRUDOverload && effect.resourceKind != null ) { hookName = 'useResourceEffect'; @@ -338,7 +338,7 @@ export function commitHookEffectListUnmount( const inst = effect.inst; const destroy = inst.destroy; if (destroy !== undefined) { - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { if (effect.resourceKind == null) { inst.destroy = undefined; } @@ -358,7 +358,7 @@ export function commitHookEffectListUnmount( setIsRunningInsertionEffect(true); } } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { if ( effect.resourceKind === ResourceEffectIdentityKind && effect.inst.resource != null diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 9d97710003317..87f601dae597d 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -38,7 +38,7 @@ import { enableSchedulingProfiler, enableTransitionTracing, enableUseEffectEventHook, - enableUseResourceEffectHook, + enableUseEffectCRUDOverload, enableLegacyCache, disableLegacyMode, enableNoCloningMemoCache, @@ -3938,7 +3938,7 @@ export const ContextOnlyDispatcher: Dispatcher = { if (enableUseEffectEventHook) { (ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError; } -if (enableUseResourceEffectHook) { +if (enableUseEffectCRUDOverload) { (ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError; } @@ -3971,7 +3971,7 @@ const HooksDispatcherOnMount: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent; } -if (enableUseResourceEffectHook) { +if (enableUseEffectCRUDOverload) { (HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect; } @@ -4004,7 +4004,7 @@ const HooksDispatcherOnUpdate: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent; } -if (enableUseResourceEffectHook) { +if (enableUseEffectCRUDOverload) { (HooksDispatcherOnUpdate: Dispatcher).useResourceEffect = updateResourceEffect; } @@ -4038,7 +4038,7 @@ const HooksDispatcherOnRerender: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent; } -if (enableUseResourceEffectHook) { +if (enableUseEffectCRUDOverload) { (HooksDispatcherOnRerender: Dispatcher).useResourceEffect = updateResourceEffect; } @@ -4242,7 +4242,7 @@ if (__DEV__) { return mountEvent(callback); }; } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { (HooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect = function useResourceEffect( create: () => {...} | void | null, @@ -4430,7 +4430,7 @@ if (__DEV__) { return mountEvent(callback); }; } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useResourceEffect = function useResourceEffect( create: () => {...} | void | null, @@ -4617,7 +4617,7 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { (HooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect = function useResourceEffect( create: () => {...} | void | null, @@ -4804,7 +4804,7 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { (HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect = function useResourceEffect( create: () => {...} | void | null, @@ -5016,7 +5016,7 @@ if (__DEV__) { return mountEvent(callback); }; } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect = function useResourceEffect( create: () => {...} | void | null, @@ -5229,7 +5229,7 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect = function useResourceEffect( create: () => {...} | void | null, @@ -5442,7 +5442,7 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseResourceEffectHook) { + if (enableUseEffectCRUDOverload) { (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect = function useResourceEffect( create: () => {...} | void | null, diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index e31b7cefd1ed7..9ac0680fb6279 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -396,7 +396,7 @@ export type Dispatcher = { ): void, // TODO: Non-nullable once `enableUseEffectEventHook` is on everywhere. useEffectEvent?: ) => mixed>(callback: F) => F, - // TODO: Non-nullable once `enableUseResourceEffectHook` is on everywhere. + // TODO: Non-nullable once `enableUseEffectCRUDOverload` is on everywhere. useResourceEffect?: ( create: () => {...} | void | null, createDeps: Array | void | null, diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index e4febdb3e28f1..27bff1e0806c4 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -3311,7 +3311,7 @@ describe('ReactHooksWithNoopRenderer', () => { }); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload describe('useResourceEffect', () => { class Resource { isDeleted: false; @@ -3333,12 +3333,12 @@ describe('ReactHooksWithNoopRenderer', () => { } } - // @gate !enableUseResourceEffectHook + // @gate !enableUseEffectCRUDOverload it('is null when flag is disabled', async () => { expect(useResourceEffect).toBeUndefined(); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('validates create return value', async () => { function App({id}) { useResourceEffect(() => { @@ -3359,7 +3359,7 @@ describe('ReactHooksWithNoopRenderer', () => { ); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('validates non-empty update deps', async () => { function App({id}) { useResourceEffect( @@ -3386,7 +3386,7 @@ describe('ReactHooksWithNoopRenderer', () => { ]); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('simple mount and update', async () => { function App({id, username}) { const opts = useMemo(() => { @@ -3443,7 +3443,7 @@ describe('ReactHooksWithNoopRenderer', () => { assertLog(['destroy(2, Jack)']); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('simple mount with no update', async () => { function App({id, username}) { const opts = useMemo(() => { @@ -3480,7 +3480,7 @@ describe('ReactHooksWithNoopRenderer', () => { assertLog(['destroy(1, Jack)']); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('calls update on every render if no deps are specified', async () => { function App({id, username}) { const opts = useMemo(() => { @@ -3523,7 +3523,7 @@ describe('ReactHooksWithNoopRenderer', () => { assertLog(['update(2, Lauren)']); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('does not unmount previous useResourceEffect between updates', async () => { function App({id}) { useResourceEffect( @@ -3562,7 +3562,7 @@ describe('ReactHooksWithNoopRenderer', () => { assertLog(['update(0)']); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('unmounts only on deletion', async () => { function App({id}) { useResourceEffect( @@ -3596,7 +3596,7 @@ describe('ReactHooksWithNoopRenderer', () => { expect(ReactNoop).toMatchRenderedOutput(null); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('unmounts on deletion', async () => { function Wrapper(props) { return ; @@ -3650,7 +3650,7 @@ describe('ReactHooksWithNoopRenderer', () => { expect(ReactNoop).toMatchRenderedOutput(null); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('handles errors in create on mount', async () => { function App({id}) { useResourceEffect( @@ -3700,7 +3700,7 @@ describe('ReactHooksWithNoopRenderer', () => { expect(ReactNoop).toMatchRenderedOutput(null); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('handles errors in create on update', async () => { function App({id}) { useResourceEffect( @@ -3744,7 +3744,7 @@ describe('ReactHooksWithNoopRenderer', () => { }).rejects.toThrow('Oops error!'); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('handles errors in destroy on update', async () => { function App({id, username}) { const opts = useMemo(() => { @@ -3800,7 +3800,7 @@ describe('ReactHooksWithNoopRenderer', () => { expect(ReactNoop).toMatchRenderedOutput(null); }); - // @gate enableUseResourceEffectHook && enableActivity + // @gate enableUseEffectCRUDOverload && enableActivity it('composes with activity', async () => { function App({id, username}) { const opts = useMemo(() => { @@ -3873,7 +3873,7 @@ describe('ReactHooksWithNoopRenderer', () => { assertLog(['destroy(0, Lauren)']); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('composes with suspense', async () => { function TextBox({text}) { return ; @@ -3991,7 +3991,7 @@ describe('ReactHooksWithNoopRenderer', () => { ); }); - // @gate enableUseResourceEffectHook + // @gate enableUseEffectCRUDOverload it('composes with other kinds of effects', async () => { let rerender; function App({id, username}) { diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index 0db0b00b3bdee..63e8576ca788c 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -40,7 +40,7 @@ import {createFastHash} from './ReactServerStreamConfig'; import { enableUseEffectEventHook, - enableUseResourceEffectHook, + enableUseEffectCRUDOverload, } from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; import { @@ -866,7 +866,7 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs if (enableUseEffectEventHook) { HooksDispatcher.useEffectEvent = useEffectEvent; } -if (enableUseResourceEffectHook) { +if (enableUseEffectCRUDOverload) { HooksDispatcher.useResourceEffect = supportsClientAPIs ? noop : clientHookNotSupported; diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index f633c7617d275..2bacbc85676fd 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -65,7 +65,7 @@ import {addTransitionType} from './ReactTransitionType'; import {act} from './ReactAct'; import {captureOwnerStack} from './ReactOwnerStack'; import * as ReactCompilerRuntime from './ReactCompilerRuntime'; -import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags'; +import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags'; const Children = { map, @@ -134,4 +134,4 @@ export { }; export const experimental_useResourceEffect: typeof useResourceEffect | void = - enableUseResourceEffectHook ? useResourceEffect : undefined; + enableUseEffectCRUDOverload ? useResourceEffect : undefined; diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index ff45b5415c416..677e0d705b8aa 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -18,7 +18,7 @@ import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols'; import ReactSharedInternals from 'shared/ReactSharedInternals'; -import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags'; +import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags'; type BasicStateAction = (S => S) | S; type Dispatch = A => void; @@ -208,7 +208,7 @@ export function useResourceEffect( updateDeps: Array | void | null, destroy: ((resource: {...} | void | null) => void) | void, ): void { - if (!enableUseResourceEffectHook) { + if (!enableUseEffectCRUDOverload) { throw new Error('Not implemented.'); } const dispatcher = resolveDispatcher(); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 290d5ba159d68..1cb1cb4cf7dc0 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -150,7 +150,7 @@ export const enableInfiniteRenderLoopDetection = false; /** * Experimental new hook for better managing resources in effects. */ -export const enableUseResourceEffectHook = false; +export const enableUseEffectCRUDOverload = false; export const enableFastAddPropertiesInDiffing = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js index 6aaefdfa336fe..001b3d64c06dc 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js @@ -24,7 +24,7 @@ export const enablePersistedModeClonedFlag = __VARIANT__; export const enableShallowPropDiffing = __VARIANT__; export const passChildrenWhenCloningPersistedNodes = __VARIANT__; export const enableSiblingPrerendering = __VARIANT__; -export const enableUseResourceEffectHook = __VARIANT__; +export const enableUseEffectCRUDOverload = __VARIANT__; export const enableOwnerStacks = __VARIANT__; export const enableRemoveConsolePatches = __VARIANT__; export const enableFastAddPropertiesInDiffing = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 878d344a42a41..6e82185b5d98c 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -24,7 +24,7 @@ export const { enableObjectFiber, enablePersistedModeClonedFlag, enableShallowPropDiffing, - enableUseResourceEffectHook, + enableUseEffectCRUDOverload, passChildrenWhenCloningPersistedNodes, enableSiblingPrerendering, enableOwnerStacks, diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index a3202c0b80049..3f373e998cd8e 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -63,7 +63,7 @@ export const retryLaneExpirationMs = 5000; export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; export const enableSiblingPrerendering = true; -export const enableUseResourceEffectHook = false; +export const enableUseEffectCRUDOverload = false; export const enableHydrationLaneScheduling = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 6bc085f540b23..da9b8a2612c9b 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -64,7 +64,7 @@ export const renameElementSymbol = true; export const enableShallowPropDiffing = false; export const enableSiblingPrerendering = true; -export const enableUseResourceEffectHook = false; +export const enableUseEffectCRUDOverload = false; export const enableYieldingBeforePassive = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index d62a59122f82c..8c20cc28deb15 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -62,7 +62,7 @@ export const retryLaneExpirationMs = 5000; export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; export const enableSiblingPrerendering = true; -export const enableUseResourceEffectHook = true; +export const enableUseEffectCRUDOverload = true; export const enableHydrationLaneScheduling = true; export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 301c01a018090..bc30992eb6eb5 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -74,7 +74,7 @@ export const enableOwnerStacks = false; export const enableShallowPropDiffing = false; export const enableSiblingPrerendering = true; -export const enableUseResourceEffectHook = false; +export const enableUseEffectCRUDOverload = false; export const enableHydrationLaneScheduling = true; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index e6fd46d0b1fb4..3cab1318c490e 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -36,7 +36,7 @@ export const enableSchedulingProfiler = __VARIANT__; export const enableInfiniteRenderLoopDetection = __VARIANT__; export const enableSiblingPrerendering = __VARIANT__; -export const enableUseResourceEffectHook = __VARIANT__; +export const enableUseEffectCRUDOverload = __VARIANT__; export const enableRemoveConsolePatches = __VARIANT__; export const enableFastAddPropertiesInDiffing = __VARIANT__; export const enableViewTransition = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index dcb77f9d84b5e..57be83577c956 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -29,7 +29,7 @@ export const { enableSiblingPrerendering, enableTransitionTracing, enableTrustedTypesIntegration, - enableUseResourceEffectHook, + enableUseEffectCRUDOverload, favorSafetyOverHydrationPerf, renameElementSymbol, retryLaneExpirationMs, From 2c5fd26c07c0fb94ff21a6c10c5a757ef3c5d6a4 Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 11 Feb 2025 14:18:50 -0500 Subject: [PATCH 17/73] [crud] Merge useResourceEffect into useEffect (#32205) Merges the useResourceEffect API into useEffect while keeping the underlying implementation the same. useResourceEffect will be removed in the next diff. To fork between behavior we rely on a `typeof` check for the updater or destroy function in addition to the CRUD feature flag. This does now have to be checked every time (instead of inlined statically like before due to them being different hooks) which will incur some non-zero amount (possibly negligble) of overhead for every effect. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32205). * #32206 * __->__ #32205 --- .../react-debug-tools/src/ReactDebugHooks.js | 7 +- .../ReactDOMServerIntegrationHooks-test.js | 6 +- .../src/ReactFiberCallUserSpace.js | 2 +- .../src/ReactFiberCommitEffects.js | 41 +-- .../react-reconciler/src/ReactFiberHooks.js | 268 ++++++++++++++---- .../src/ReactInternalTypes.js | 7 +- .../ReactHooksWithNoopRenderer-test.js | 70 +++-- packages/react/src/ReactHooks.js | 26 +- scripts/error-codes/codes.json | 3 +- 9 files changed, 296 insertions(+), 134 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 9c310723b2605..f45ccfecdcaec 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -373,8 +373,11 @@ function useInsertionEffect( } function useEffect( - create: () => (() => void) | void, - inputs: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { nextHook(); hookLog.push({ diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js index bce830ddf0647..840d6c5b15d5e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js @@ -27,7 +27,6 @@ let useRef; let useImperativeHandle; let useInsertionEffect; let useLayoutEffect; -let useResourceEffect; let useDebugValue; let forwardRef; let yieldedValues; @@ -52,7 +51,6 @@ function initModules() { useImperativeHandle = React.useImperativeHandle; useInsertionEffect = React.useInsertionEffect; useLayoutEffect = React.useLayoutEffect; - useResourceEffect = React.experimental_useResourceEffect; forwardRef = React.forwardRef; yieldedValues = []; @@ -655,7 +653,7 @@ describe('ReactDOMServerHooks', () => { }); }); - describe('useResourceEffect', () => { + describe('useEffect with CRUD overload', () => { gate(flags => { if (flags.enableUseEffectCRUDOverload) { const yields = []; @@ -663,7 +661,7 @@ describe('ReactDOMServerHooks', () => { 'should ignore resource effects on the server', async render => { function Counter(props) { - useResourceEffect( + useEffect( () => { yieldValue('created on client'); return {resource_counter: props.count}; diff --git a/packages/react-reconciler/src/ReactFiberCallUserSpace.js b/packages/react-reconciler/src/ReactFiberCallUserSpace.js index 25ede645b5e7f..a9b5590f387b7 100644 --- a/packages/react-reconciler/src/ReactFiberCallUserSpace.js +++ b/packages/react-reconciler/src/ReactFiberCallUserSpace.js @@ -254,7 +254,7 @@ const callDestroy = { export const callDestroyInDEV: ( current: Fiber, nearestMountedAncestor: Fiber | null, - destroy: () => void, + destroy: (() => void) | (({...}) => void), ) => void = __DEV__ ? // We use this technique to trick minifiers to preserve the function name. (callDestroy['react-stack-bottom-frame'].bind(callDestroy): any) diff --git a/packages/react-reconciler/src/ReactFiberCommitEffects.js b/packages/react-reconciler/src/ReactFiberCommitEffects.js index 29e5de2817201..05cf17a34273e 100644 --- a/packages/react-reconciler/src/ReactFiberCommitEffects.js +++ b/packages/react-reconciler/src/ReactFiberCommitEffects.js @@ -170,8 +170,9 @@ export function commitHookEffectListMount( ); if (effect.inst.resource == null) { console.error( - 'useResourceEffect must provide a callback which returns a resource. ' + - 'If a managed resource is not needed here, use useEffect. Received %s', + 'useEffect must provide a callback which returns a resource. ' + + 'If a managed resource is not needed here, do not provide an updater or ' + + 'destroy callback. Received %s', effect.inst.resource, ); } @@ -261,11 +262,6 @@ export function commitHookEffectListMount( hookName = 'useLayoutEffect'; } else if ((effect.tag & HookInsertion) !== NoFlags) { hookName = 'useInsertionEffect'; - } else if ( - enableUseEffectCRUDOverload && - effect.resourceKind != null - ) { - hookName = 'useResourceEffect'; } else { hookName = 'useEffect'; } @@ -363,7 +359,7 @@ export function commitHookEffectListUnmount( effect.resourceKind === ResourceEffectIdentityKind && effect.inst.resource != null ) { - safelyCallDestroyWithResource( + safelyCallDestroy( finishedWork, nearestMountedAncestor, destroy, @@ -1015,32 +1011,11 @@ export function safelyDetachRef( function safelyCallDestroy( current: Fiber, nearestMountedAncestor: Fiber | null, - destroy: () => void, -) { - if (__DEV__) { - runWithFiberInDEV( - current, - callDestroyInDEV, - current, - nearestMountedAncestor, - destroy, - ); - } else { - try { - destroy(); - } catch (error) { - captureCommitPhaseError(current, nearestMountedAncestor, error); - } - } -} - -function safelyCallDestroyWithResource( - current: Fiber, - nearestMountedAncestor: Fiber | null, - destroy: ({...}) => void, - resource: {...}, + destroy: (() => void) | (({...}) => void), + resource?: {...} | void | null, ) { - const destroy_ = destroy.bind(null, resource); + // $FlowFixMe[extra-arg] @poteto this is safe either way because the extra arg is ignored if it's not a CRUD effect + const destroy_ = resource == null ? destroy : destroy.bind(null, resource); if (__DEV__) { runWithFiberInDEV( current, diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 87f601dae597d..976657d4c68f7 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -2523,12 +2523,15 @@ function pushSimpleEffect( tag: HookFlags, inst: EffectInstance, create: () => (() => void) | void, - deps: Array | void | null, + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): Effect { const effect: Effect = { tag, create, - deps, + deps: createDeps, inst, // Circular next: (null: any), @@ -2608,10 +2611,13 @@ function mountEffectImpl( fiberFlags: Flags, hookFlags: HookFlags, create: () => (() => void) | void, - deps: Array | void | null, + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { const hook = mountWorkInProgressHook(); - const nextDeps = deps === undefined ? null : deps; + const nextDeps = createDeps === undefined ? null : createDeps; currentlyRenderingFiber.flags |= fiberFlags; hook.memoizedState = pushSimpleEffect( HookHasEffect | hookFlags, @@ -2662,35 +2668,89 @@ function updateEffectImpl( } function mountEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { if ( __DEV__ && (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode && (currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode ) { - mountEffectImpl( - MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, - HookPassive, - create, - deps, - ); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + mountResourceEffectImpl( + MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, + HookPassive, + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + mountEffectImpl( + MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, + HookPassive, + // $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it. + create, + createDeps, + ); + } } else { - mountEffectImpl( - PassiveEffect | PassiveStaticEffect, - HookPassive, - create, - deps, - ); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + mountResourceEffectImpl( + PassiveEffect | PassiveStaticEffect, + HookPassive, + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + mountEffectImpl( + PassiveEffect | PassiveStaticEffect, + HookPassive, + // $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it. + create, + createDeps, + ); + } } } function updateEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { - updateEffectImpl(PassiveEffect, HookPassive, create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + updateResourceEffectImpl( + PassiveEffect, + HookPassive, + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + // $FlowFixMe[incompatible-call] @poteto it's not possible to narrow `create` without calling it. + updateEffectImpl(PassiveEffect, HookPassive, create, createDeps); + } } function mountResourceEffect( @@ -2705,15 +2765,6 @@ function mountResourceEffect( (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode && (currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode ) { - mountResourceEffectImpl( - MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, - HookPassive, - create, - createDeps, - update, - updateDeps, - destroy, - ); } else { mountResourceEffectImpl( PassiveEffect | PassiveStaticEffect, @@ -4087,13 +4138,30 @@ if (__DEV__) { return readContext(context); }, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useEffect'; mountHookTypesDev(); - checkDepsAreArrayDev(deps); - return mountEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + checkDepsAreNonEmptyArrayDev(updateDeps); + return mountResourceEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + checkDepsAreArrayDev(createDeps); + return mountEffect(create, createDeps); + } }, useImperativeHandle( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, @@ -4280,12 +4348,28 @@ if (__DEV__) { return readContext(context); }, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useEffect'; updateHookTypesDev(); - return mountEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + return mountResourceEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + return mountEffect(create, createDeps); + } }, useImperativeHandle( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, @@ -4467,12 +4551,28 @@ if (__DEV__) { return readContext(context); }, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useEffect'; updateHookTypesDev(); - return updateEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + return updateResourceEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + return updateEffect(create, createDeps); + } }, useImperativeHandle( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, @@ -4654,12 +4754,28 @@ if (__DEV__) { return readContext(context); }, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useEffect'; updateHookTypesDev(); - return updateEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + return updateResourceEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + return updateEffect(create, createDeps); + } }, useImperativeHandle( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, @@ -4847,13 +4963,29 @@ if (__DEV__) { return readContext(context); }, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useEffect'; warnInvalidHookAccess(); mountHookTypesDev(); - return mountEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + return mountResourceEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + return mountEffect(create, createDeps); + } }, useImperativeHandle( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, @@ -5060,13 +5192,29 @@ if (__DEV__) { return readContext(context); }, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useEffect'; warnInvalidHookAccess(); updateHookTypesDev(); - return updateEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + return updateResourceEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + return updateEffect(create, createDeps); + } }, useImperativeHandle( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, @@ -5273,13 +5421,29 @@ if (__DEV__) { return readContext(context); }, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { currentHookNameInDev = 'useEffect'; warnInvalidHookAccess(); updateHookTypesDev(); - return updateEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + return updateResourceEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else { + return updateEffect(create, createDeps); + } }, useImperativeHandle( ref: {current: T | null} | ((inst: T | null) => mixed) | null | void, diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 9ac0680fb6279..756d88c4a5d15 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -391,8 +391,11 @@ export type Dispatcher = { useContext(context: ReactContext): T, useRef(initialValue: T): {current: T}, useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void, // TODO: Non-nullable once `enableUseEffectEventHook` is on everywhere. useEffectEvent?: ) => mixed>(callback: F) => F, diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index 27bff1e0806c4..47fd534e683a9 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -41,7 +41,6 @@ let waitFor; let waitForThrow; let waitForPaint; let assertLog; -let useResourceEffect; let assertConsoleErrorDev; describe('ReactHooksWithNoopRenderer', () => { @@ -70,7 +69,6 @@ describe('ReactHooksWithNoopRenderer', () => { useDeferredValue = React.useDeferredValue; Suspense = React.Suspense; Activity = React.unstable_Activity; - useResourceEffect = React.experimental_useResourceEffect; ContinuousEventPriority = require('react-reconciler/constants').ContinuousEventPriority; if (gate(flags => flags.enableSuspenseList)) { @@ -3312,7 +3310,7 @@ describe('ReactHooksWithNoopRenderer', () => { }); // @gate enableUseEffectCRUDOverload - describe('useResourceEffect', () => { + describe('useEffect CRUD overload', () => { class Resource { isDeleted: false; id: string; @@ -3335,34 +3333,34 @@ describe('ReactHooksWithNoopRenderer', () => { // @gate !enableUseEffectCRUDOverload it('is null when flag is disabled', async () => { - expect(useResourceEffect).toBeUndefined(); - }); - - // @gate enableUseEffectCRUDOverload - it('validates create return value', async () => { function App({id}) { - useResourceEffect(() => { - Scheduler.log(`create(${id})`); - }, [id]); + useEffect( + () => { + Scheduler.log(`create(${id})`); + return {}; + }, + [id], + () => { + Scheduler.log('update'); + }, + [], + ); return null; } - await act(() => { - ReactNoop.render(); - }); - assertConsoleErrorDev( - [ - 'useResourceEffect must provide a callback which returns a resource. ' + - 'If a managed resource is not needed here, use useEffect. Received undefined', - ], - {withoutStack: true}, + await expect(async () => { + await act(() => { + ReactNoop.render(); + }); + }).rejects.toThrow( + 'useEffect CRUD overload is not enabled in this build of React.', ); }); // @gate enableUseEffectCRUDOverload it('validates non-empty update deps', async () => { function App({id}) { - useResourceEffect( + useEffect( () => { Scheduler.log(`create(${id})`); return {}; @@ -3380,7 +3378,7 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.render(); }); assertConsoleErrorDev([ - 'useResourceEffect received a dependency array with no dependencies. ' + + 'useEffect received a dependency array with no dependencies. ' + 'When specified, the dependency array must have at least one dependency.\n' + ' in App (at **)', ]); @@ -3392,7 +3390,7 @@ describe('ReactHooksWithNoopRenderer', () => { const opts = useMemo(() => { return {username}; }, [username]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`create(${resource.id}, ${resource.opts.username})`); @@ -3449,7 +3447,7 @@ describe('ReactHooksWithNoopRenderer', () => { const opts = useMemo(() => { return {username}; }, [username]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`create(${resource.id}, ${resource.opts.username})`); @@ -3486,7 +3484,7 @@ describe('ReactHooksWithNoopRenderer', () => { const opts = useMemo(() => { return {username}; }, [username]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`create(${resource.id}, ${resource.opts.username})`); @@ -3524,9 +3522,9 @@ describe('ReactHooksWithNoopRenderer', () => { }); // @gate enableUseEffectCRUDOverload - it('does not unmount previous useResourceEffect between updates', async () => { + it('does not unmount previous useEffect between updates', async () => { function App({id}) { - useResourceEffect( + useEffect( () => { const resource = new Resource(id); Scheduler.log(`create(${resource.id})`); @@ -3565,7 +3563,7 @@ describe('ReactHooksWithNoopRenderer', () => { // @gate enableUseEffectCRUDOverload it('unmounts only on deletion', async () => { function App({id}) { - useResourceEffect( + useEffect( () => { const resource = new Resource(id); Scheduler.log(`create(${resource.id})`); @@ -3605,7 +3603,7 @@ describe('ReactHooksWithNoopRenderer', () => { const opts = useMemo(() => { return {username}; }, [username]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`create(${resource.id}, ${resource.opts.username})`); @@ -3653,7 +3651,7 @@ describe('ReactHooksWithNoopRenderer', () => { // @gate enableUseEffectCRUDOverload it('handles errors in create on mount', async () => { function App({id}) { - useResourceEffect( + useEffect( () => { Scheduler.log(`Mount A [${id}]`); return {}; @@ -3665,7 +3663,7 @@ describe('ReactHooksWithNoopRenderer', () => { Scheduler.log(`Unmount A [${id}]`); }, ); - useResourceEffect( + useEffect( () => { Scheduler.log('Oops!'); throw new Error('Oops!'); @@ -3703,7 +3701,7 @@ describe('ReactHooksWithNoopRenderer', () => { // @gate enableUseEffectCRUDOverload it('handles errors in create on update', async () => { function App({id}) { - useResourceEffect( + useEffect( () => { Scheduler.log(`Mount A [${id}]`); return {}; @@ -3750,7 +3748,7 @@ describe('ReactHooksWithNoopRenderer', () => { const opts = useMemo(() => { return {username}; }, [username]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`Mount A [${id}, ${resource.opts.username}]`); @@ -3806,7 +3804,7 @@ describe('ReactHooksWithNoopRenderer', () => { const opts = useMemo(() => { return {username}; }, [username]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`create(${resource.id}, ${resource.opts.username})`); @@ -3885,7 +3883,7 @@ describe('ReactHooksWithNoopRenderer', () => { const opts = useMemo(() => { return {username}; }, [username]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`create(${resource.id}, ${resource.opts.username})`); @@ -4003,7 +4001,7 @@ describe('ReactHooksWithNoopRenderer', () => { useEffect(() => { Scheduler.log(`useEffect(${count})`); }, [count]); - useResourceEffect( + useEffect( () => { const resource = new Resource(id, opts); Scheduler.log(`create(${resource.id}, ${resource.opts.username})`); diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 677e0d705b8aa..2fe6d2b85bcf1 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -87,11 +87,31 @@ export function useRef(initialValue: T): {current: T} { } export function useEffect( - create: () => (() => void) | void, - deps: Array | void | null, + create: (() => (() => void) | void) | (() => {...} | void | null), + createDeps: Array | void | null, + update?: ((resource: {...} | void | null) => void) | void, + updateDeps?: Array | void | null, + destroy?: ((resource: {...} | void | null) => void) | void, ): void { const dispatcher = resolveDispatcher(); - return dispatcher.useEffect(create, deps); + if ( + enableUseEffectCRUDOverload && + (typeof update === 'function' || typeof destroy === 'function') + ) { + // $FlowFixMe[not-a-function] This is unstable, thus optional + return dispatcher.useEffect( + create, + createDeps, + update, + updateDeps, + destroy, + ); + } else if (typeof update === 'function') { + throw new Error( + 'useEffect CRUD overload is not enabled in this build of React.', + ); + } + return dispatcher.useEffect(create, createDeps); } export function useInsertionEffect( diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 8fa3d190ecb70..6ab654f1d35e5 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -530,5 +530,6 @@ "542": "Suspense Exception: This is not a real error! It's an implementation detail of `useActionState` to interrupt the current render. You must either rethrow it immediately, or move the `useActionState` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.\n\nTo handle async errors, wrap your component in an error boundary.", "543": "Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React.", "544": "Found a pair with an auto name. This is a bug in React.", - "545": "The %s tag may only be rendered once." + "545": "The %s tag may only be rendered once.", + "546": "useEffect CRUD overload is not enabled in this build of React." } From a69b80d07e5d1bf363ed15d6209a55b35e0765c2 Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 11 Feb 2025 14:19:34 -0500 Subject: [PATCH 18/73] [crud] Remove useResourceEffect (#32206) Removes useResourceEffect. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32206). * __->__ #32206 * #32205 --- .../react-debug-tools/src/ReactDebugHooks.js | 22 --- .../react-reconciler/src/ReactFiberHooks.js | 158 ------------------ .../src/ReactInternalTypes.js | 9 - packages/react-server/src/ReactFizzHooks.js | 10 +- packages/react/index.development.js | 1 - .../react/index.experimental.development.js | 1 - packages/react/index.fb.js | 1 - packages/react/src/ReactClient.js | 5 - packages/react/src/ReactHooks.js | 21 --- 9 files changed, 1 insertion(+), 227 deletions(-) diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index f45ccfecdcaec..114080d03e921 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -128,9 +128,6 @@ function getPrimitiveStackCache(): Map> { Dispatcher.useId(); - if (typeof Dispatcher.useResourceEffect === 'function') { - Dispatcher.useResourceEffect(() => ({}), []); - } if (typeof Dispatcher.useEffectEvent === 'function') { Dispatcher.useEffectEvent((args: empty) => {}); } @@ -741,24 +738,6 @@ function useHostTransitionStatus(): TransitionStatus { return status; } -function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, -) { - nextHook(); - hookLog.push({ - displayName: null, - primitive: 'ResourceEffect', - stackError: new Error(), - value: create, - debugInfo: null, - dispatcherHookName: 'ResourceEffect', - }); -} - function useEffectEvent) => mixed>(callback: F): F { nextHook(); hookLog.push({ @@ -798,7 +777,6 @@ const Dispatcher: DispatcherType = { useActionState, useHostTransitionStatus, useEffectEvent, - useResourceEffect, }; // create a proxy to throw a custom error diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 976657d4c68f7..2ab41770bd0ac 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -3989,9 +3989,6 @@ export const ContextOnlyDispatcher: Dispatcher = { if (enableUseEffectEventHook) { (ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError; } -if (enableUseEffectCRUDOverload) { - (ContextOnlyDispatcher: Dispatcher).useResourceEffect = throwInvalidHookError; -} const HooksDispatcherOnMount: Dispatcher = { readContext, @@ -4022,9 +4019,6 @@ const HooksDispatcherOnMount: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent; } -if (enableUseEffectCRUDOverload) { - (HooksDispatcherOnMount: Dispatcher).useResourceEffect = mountResourceEffect; -} const HooksDispatcherOnUpdate: Dispatcher = { readContext, @@ -4055,10 +4049,6 @@ const HooksDispatcherOnUpdate: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent; } -if (enableUseEffectCRUDOverload) { - (HooksDispatcherOnUpdate: Dispatcher).useResourceEffect = - updateResourceEffect; -} const HooksDispatcherOnRerender: Dispatcher = { readContext, @@ -4089,10 +4079,6 @@ const HooksDispatcherOnRerender: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent; } -if (enableUseEffectCRUDOverload) { - (HooksDispatcherOnRerender: Dispatcher).useResourceEffect = - updateResourceEffect; -} let HooksDispatcherOnMountInDEV: Dispatcher | null = null; let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null; @@ -4310,27 +4296,6 @@ if (__DEV__) { return mountEvent(callback); }; } - if (enableUseEffectCRUDOverload) { - (HooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect = - function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ): void { - currentHookNameInDev = 'useResourceEffect'; - mountHookTypesDev(); - checkDepsAreNonEmptyArrayDev(updateDeps); - return mountResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); - }; - } HooksDispatcherOnMountWithHookTypesInDEV = { readContext(context: ReactContext): T { @@ -4514,26 +4479,6 @@ if (__DEV__) { return mountEvent(callback); }; } - if (enableUseEffectCRUDOverload) { - (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useResourceEffect = - function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ): void { - currentHookNameInDev = 'useResourceEffect'; - updateHookTypesDev(); - return mountResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); - }; - } HooksDispatcherOnUpdateInDEV = { readContext(context: ReactContext): T { @@ -4717,26 +4662,6 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseEffectCRUDOverload) { - (HooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect = - function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ) { - currentHookNameInDev = 'useResourceEffect'; - updateHookTypesDev(); - return updateResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); - }; - } HooksDispatcherOnRerenderInDEV = { readContext(context: ReactContext): T { @@ -4920,26 +4845,6 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseEffectCRUDOverload) { - (HooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect = - function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ) { - currentHookNameInDev = 'useResourceEffect'; - updateHookTypesDev(); - return updateResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); - }; - } InvalidNestedHooksDispatcherOnMountInDEV = { readContext(context: ReactContext): T { @@ -5148,27 +5053,6 @@ if (__DEV__) { return mountEvent(callback); }; } - if (enableUseEffectCRUDOverload) { - (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useResourceEffect = - function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ): void { - currentHookNameInDev = 'useResourceEffect'; - warnInvalidHookAccess(); - mountHookTypesDev(); - return mountResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); - }; - } InvalidNestedHooksDispatcherOnUpdateInDEV = { readContext(context: ReactContext): T { @@ -5377,27 +5261,6 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseEffectCRUDOverload) { - (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useResourceEffect = - function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ) { - currentHookNameInDev = 'useResourceEffect'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return updateResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); - }; - } InvalidNestedHooksDispatcherOnRerenderInDEV = { readContext(context: ReactContext): T { @@ -5606,25 +5469,4 @@ if (__DEV__) { return updateEvent(callback); }; } - if (enableUseEffectCRUDOverload) { - (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useResourceEffect = - function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ) { - currentHookNameInDev = 'useResourceEffect'; - warnInvalidHookAccess(); - updateHookTypesDev(); - return updateResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); - }; - } } diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 756d88c4a5d15..98e7d4deefea3 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -47,7 +47,6 @@ export type HookType = | 'useRef' | 'useEffect' | 'useEffectEvent' - | 'useResourceEffect' | 'useInsertionEffect' | 'useLayoutEffect' | 'useCallback' @@ -399,14 +398,6 @@ export type Dispatcher = { ): void, // TODO: Non-nullable once `enableUseEffectEventHook` is on everywhere. useEffectEvent?: ) => mixed>(callback: F) => F, - // TODO: Non-nullable once `enableUseEffectCRUDOverload` is on everywhere. - useResourceEffect?: ( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, - ) => void, useInsertionEffect( create: () => (() => void) | void, deps: Array | void | null, diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index 63e8576ca788c..a0ec1c7414982 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -38,10 +38,7 @@ import { } from './ReactFizzConfig'; import {createFastHash} from './ReactServerStreamConfig'; -import { - enableUseEffectEventHook, - enableUseEffectCRUDOverload, -} from 'shared/ReactFeatureFlags'; +import {enableUseEffectEventHook} from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; import { REACT_CONTEXT_TYPE, @@ -866,11 +863,6 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs if (enableUseEffectEventHook) { HooksDispatcher.useEffectEvent = useEffectEvent; } -if (enableUseEffectCRUDOverload) { - HooksDispatcher.useResourceEffect = supportsClientAPIs - ? noop - : clientHookNotSupported; -} export let currentResumableState: null | ResumableState = (null: any); export function setCurrentResumableState( diff --git a/packages/react/index.development.js b/packages/react/index.development.js index 809e940f070ba..fa79633001a06 100644 --- a/packages/react/index.development.js +++ b/packages/react/index.development.js @@ -59,7 +59,6 @@ export { useDeferredValue, useEffect, experimental_useEffectEvent, - experimental_useResourceEffect, useImperativeHandle, useInsertionEffect, useLayoutEffect, diff --git a/packages/react/index.experimental.development.js b/packages/react/index.experimental.development.js index 49c98bb208acb..6074b683b781d 100644 --- a/packages/react/index.experimental.development.js +++ b/packages/react/index.experimental.development.js @@ -42,7 +42,6 @@ export { useDeferredValue, useEffect, experimental_useEffectEvent, - experimental_useResourceEffect, useImperativeHandle, useInsertionEffect, useLayoutEffect, diff --git a/packages/react/index.fb.js b/packages/react/index.fb.js index 8e11bade4df59..29133df24f3a7 100644 --- a/packages/react/index.fb.js +++ b/packages/react/index.fb.js @@ -21,7 +21,6 @@ export { createElement, createRef, experimental_useEffectEvent, - experimental_useResourceEffect, forwardRef, Fragment, isValidElement, diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index 2bacbc85676fd..715ea8ab47c35 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -41,7 +41,6 @@ import { useContext, useEffect, useEffectEvent, - useResourceEffect, useImperativeHandle, useDebugValue, useInsertionEffect, @@ -65,7 +64,6 @@ import {addTransitionType} from './ReactTransitionType'; import {act} from './ReactAct'; import {captureOwnerStack} from './ReactOwnerStack'; import * as ReactCompilerRuntime from './ReactCompilerRuntime'; -import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags'; const Children = { map, @@ -132,6 +130,3 @@ export { act, // DEV-only captureOwnerStack, // DEV-only }; - -export const experimental_useResourceEffect: typeof useResourceEffect | void = - enableUseEffectCRUDOverload ? useResourceEffect : undefined; diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 2fe6d2b85bcf1..06d61d223874d 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -221,27 +221,6 @@ export function useEffectEvent) => mixed>( return dispatcher.useEffectEvent(callback); } -export function useResourceEffect( - create: () => {...} | void | null, - createDeps: Array | void | null, - update: ((resource: {...} | void | null) => void) | void, - updateDeps: Array | void | null, - destroy: ((resource: {...} | void | null) => void) | void, -): void { - if (!enableUseEffectCRUDOverload) { - throw new Error('Not implemented.'); - } - const dispatcher = resolveDispatcher(); - // $FlowFixMe[not-a-function] This is unstable, thus optional - return dispatcher.useResourceEffect( - create, - createDeps, - update, - updateDeps, - destroy, - ); -} - export function useOptimistic( passthrough: S, reducer: ?(S, A) => S, From 192555bb0ed88db30f91c58651c421f178f90384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Tue, 11 Feb 2025 17:01:04 -0500 Subject: [PATCH 19/73] Added dev-only warning for null/undefined create in use*Effect (#32355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #32354. Re-creation of #15197: adds a dev-only warning if `create == null` to the three `use*Effect` functions: * `useEffect` * `useInsertionEffect` * `useLayoutEffect` Updates the warning to match the same text given in the `react/exhaustive-deps` lint rule. ## How did you test this change? I applied the changes manually within `node_modules/` on a local clone of https://github.com/JoshuaKGoldberg/repros/tree/react-use-effect-no-arguments. Please pardon me for opening a PR addressing a not-accepted issue. I was excited to get back to #15194 -> #15197 now that I have time. 🙂 --------- Co-authored-by: lauren --- packages/react/src/ReactHooks.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index 06d61d223874d..ba6b0ffbcb71e 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -93,6 +93,14 @@ export function useEffect( updateDeps?: Array | void | null, destroy?: ((resource: {...} | void | null) => void) | void, ): void { + if (__DEV__) { + if (create == null) { + console.warn( + 'React Hook useEffect requires an effect callback. Did you forget to pass a callback to the hook?', + ); + } + } + const dispatcher = resolveDispatcher(); if ( enableUseEffectCRUDOverload && @@ -118,6 +126,14 @@ export function useInsertionEffect( create: () => (() => void) | void, deps: Array | void | null, ): void { + if (__DEV__) { + if (create == null) { + console.warn( + 'React Hook useInsertionEffect requires an effect callback. Did you forget to pass a callback to the hook?', + ); + } + } + const dispatcher = resolveDispatcher(); return dispatcher.useInsertionEffect(create, deps); } @@ -126,6 +142,14 @@ export function useLayoutEffect( create: () => (() => void) | void, deps: Array | void | null, ): void { + if (__DEV__) { + if (create == null) { + console.warn( + 'React Hook useLayoutEffect requires an effect callback. Did you forget to pass a callback to the hook?', + ); + } + } + const dispatcher = resolveDispatcher(); return dispatcher.useLayoutEffect(create, deps); } From f83903bfcc5a61811bd1b69b14f0ebbac4754462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 12 Feb 2025 13:52:57 +0000 Subject: [PATCH 20/73] [RN] Set up test to create public instances lazily in Fabric (#32363) ## Summary In React Native, public instances and internal host nodes are not represented by the same object (ReactNativeElement & shadow nodes vs. just DOM elements), and the only one that's required for rendering is the shadow node. Public instances are generally only necessary when accessed via refs or events, and that usually happens for a small amount of components in the tree. This implements an optimization to create the public instance on demand, instead of eagerly creating it when creating the host node. We expect this to improve performance by reducing the logic we do per node and the number of object allocations. ## How did you test this change? Manually synced the changes to React Native and run Fantom tests and benchmarks, with the flag enabled and disabled. All tests pass in both cases, and benchmarks show a slight but consistent performance improvement. --- .../src/ReactFiberConfigFabric.js | 69 ++++++++++++++----- packages/shared/ReactFeatureFlags.js | 2 + .../ReactFeatureFlags.native-fb-dynamic.js | 1 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + ...actFeatureFlags.test-renderer.native-fb.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../forks/ReactFeatureFlags.www-dynamic.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 2 + 10 files changed, 61 insertions(+), 19 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index d602ab05efa81..d28b3400c66c3 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -57,7 +57,10 @@ import { getInspectorDataForInstance, } from './ReactNativeFiberInspector'; -import {passChildrenWhenCloningPersistedNodes} from 'shared/ReactFeatureFlags'; +import { + passChildrenWhenCloningPersistedNodes, + enableLazyPublicInstanceInFabric, +} from 'shared/ReactFeatureFlags'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import type {ReactContext} from 'shared/ReactTypes'; @@ -93,8 +96,11 @@ export type Instance = { currentProps: Props, // Reference to the React handle (the fiber) internalInstanceHandle: InternalInstanceHandle, - // Exposed through refs. - publicInstance: PublicInstance, + // Exposed through refs. Potentially lazily created. + publicInstance: PublicInstance | null, + // This is only necessary to lazily create `publicInstance`. + // Will be set to `null` after that is created. + publicRootInstance?: PublicRootInstance | null, }, }; export type TextInstance = { @@ -186,23 +192,37 @@ export function createInstance( internalInstanceHandle, // internalInstanceHandle ); - const component = createPublicInstance( - tag, - viewConfig, - internalInstanceHandle, - rootContainerInstance.publicInstance, - ); - - return { - node: node, - canonical: { - nativeTag: tag, + if (enableLazyPublicInstanceInFabric) { + return { + node: node, + canonical: { + nativeTag: tag, + viewConfig, + currentProps: props, + internalInstanceHandle, + publicInstance: null, + publicRootInstance: rootContainerInstance.publicInstance, + }, + }; + } else { + const component = createPublicInstance( + tag, viewConfig, - currentProps: props, internalInstanceHandle, - publicInstance: component, - }, - }; + rootContainerInstance.publicInstance, + ); + + return { + node: node, + canonical: { + nativeTag: tag, + viewConfig, + currentProps: props, + internalInstanceHandle, + publicInstance: component, + }, + }; + } } export function createTextInstance( @@ -277,7 +297,18 @@ export function getChildHostContext( } export function getPublicInstance(instance: Instance): null | PublicInstance { - if (instance.canonical != null && instance.canonical.publicInstance != null) { + if (instance.canonical != null) { + if (instance.canonical.publicInstance == null) { + instance.canonical.publicInstance = createPublicInstance( + instance.canonical.nativeTag, + instance.canonical.viewConfig, + instance.canonical.internalInstanceHandle, + instance.canonical.publicRootInstance ?? null, + ); + // This was only necessary to create the public instance. + instance.canonical.publicRootInstance = null; + } + return instance.canonical.publicInstance; } diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 1cb1cb4cf7dc0..fbdfe5a468516 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -154,6 +154,8 @@ export const enableUseEffectCRUDOverload = false; export const enableFastAddPropertiesInDiffing = true; +export const enableLazyPublicInstanceInFabric = false; + // ----------------------------------------------------------------------------- // Ready for next major. // diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js index 001b3d64c06dc..ffcec9334d663 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js @@ -28,3 +28,4 @@ export const enableUseEffectCRUDOverload = __VARIANT__; export const enableOwnerStacks = __VARIANT__; export const enableRemoveConsolePatches = __VARIANT__; export const enableFastAddPropertiesInDiffing = __VARIANT__; +export const enableLazyPublicInstanceInFabric = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 6e82185b5d98c..2eff113ae42f1 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -30,6 +30,7 @@ export const { enableOwnerStacks, enableRemoveConsolePatches, enableFastAddPropertiesInDiffing, + enableLazyPublicInstanceInFabric, } = dynamicFlags; // The rest of the flags are static for better dead code elimination. diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 3f373e998cd8e..bcd8b375eca67 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -72,6 +72,7 @@ export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; export const enableViewTransition = false; export const enableFastAddPropertiesInDiffing = false; +export const enableLazyPublicInstanceInFabric = false; // Profiling Only export const enableProfilerTimer = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index da9b8a2612c9b..6fcd35c40774b 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -71,6 +71,7 @@ export const enableYieldingBeforePassive = true; export const enableThrottledScheduling = false; export const enableViewTransition = false; export const enableFastAddPropertiesInDiffing = true; +export const enableLazyPublicInstanceInFabric = false; // TODO: This must be in sync with the main ReactFeatureFlags file because // the Test Renderer's value must be the same as the one used by the diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 8c20cc28deb15..743ba39a86792 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -69,6 +69,7 @@ export const enableThrottledScheduling = false; export const enableViewTransition = false; export const enableRemoveConsolePatches = false; export const enableFastAddPropertiesInDiffing = false; +export const enableLazyPublicInstanceInFabric = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index bc30992eb6eb5..26747d3fb69a9 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -84,6 +84,7 @@ export const enableThrottledScheduling = false; export const enableViewTransition = false; export const enableRemoveConsolePatches = false; export const enableFastAddPropertiesInDiffing = false; +export const enableLazyPublicInstanceInFabric = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 3cab1318c490e..d0b4404a3e946 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -39,6 +39,7 @@ export const enableSiblingPrerendering = __VARIANT__; export const enableUseEffectCRUDOverload = __VARIANT__; export const enableRemoveConsolePatches = __VARIANT__; export const enableFastAddPropertiesInDiffing = __VARIANT__; +export const enableLazyPublicInstanceInFabric = false; export const enableViewTransition = __VARIANT__; // TODO: These flags are hard-coded to the default values used in open source. diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 57be83577c956..8da74c54bd69e 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -110,5 +110,7 @@ export const disableLegacyMode = true; export const enableShallowPropDiffing = false; +export const enableLazyPublicInstanceInFabric = false; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); From d814852baf03c61b1e32b59a45c3f72e00577ac8 Mon Sep 17 00:00:00 2001 From: lauren Date: Wed, 12 Feb 2025 11:59:56 -0500 Subject: [PATCH 21/73] [compiler] Upgrade esbuild (#32368) Just a simple upgrade --- compiler/package.json | 2 +- compiler/yarn.lock | 314 +++++++++++++++++++++--------------------- 2 files changed, 158 insertions(+), 158 deletions(-) diff --git a/compiler/package.json b/compiler/package.json index b16756f0aa2c0..f1696b9b2a5c5 100644 --- a/compiler/package.json +++ b/compiler/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@tsconfig/strictest": "^2.0.5", "concurrently": "^7.4.0", - "esbuild": "^0.24.2", + "esbuild": "^0.25.0", "folder-hash": "^4.0.4", "npm-dts": "^1.3.13", "object-assign": "^4.1.1", diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 354cc199b0285..b5bd5f8eaf7b4 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -1714,130 +1714,130 @@ enabled "2.0.x" kuler "^2.0.0" -"@esbuild/aix-ppc64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461" - integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA== - -"@esbuild/android-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894" - integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg== - -"@esbuild/android-arm@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3" - integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q== - -"@esbuild/android-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb" - integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw== - -"@esbuild/darwin-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936" - integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA== - -"@esbuild/darwin-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9" - integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA== - -"@esbuild/freebsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00" - integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg== - -"@esbuild/freebsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f" - integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q== - -"@esbuild/linux-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43" - integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg== - -"@esbuild/linux-arm@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736" - integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA== - -"@esbuild/linux-ia32@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5" - integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw== - -"@esbuild/linux-loong64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc" - integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ== - -"@esbuild/linux-mips64el@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb" - integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw== - -"@esbuild/linux-ppc64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412" - integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw== - -"@esbuild/linux-riscv64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694" - integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q== - -"@esbuild/linux-s390x@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577" - integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw== - -"@esbuild/linux-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f" - integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q== - -"@esbuild/netbsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6" - integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw== - -"@esbuild/netbsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40" - integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw== - -"@esbuild/openbsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f" - integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A== - -"@esbuild/openbsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205" - integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA== - -"@esbuild/sunos-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6" - integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig== - -"@esbuild/win32-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85" - integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ== - -"@esbuild/win32-ia32@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2" - integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA== - -"@esbuild/win32-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b" - integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg== +"@esbuild/aix-ppc64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64" + integrity sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ== + +"@esbuild/android-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz#b9b8231561a1dfb94eb31f4ee056b92a985c324f" + integrity sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g== + +"@esbuild/android-arm@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz#ca6e7888942505f13e88ac9f5f7d2a72f9facd2b" + integrity sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g== + +"@esbuild/android-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz#e765ea753bac442dfc9cb53652ce8bd39d33e163" + integrity sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg== + +"@esbuild/darwin-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz#fa394164b0d89d4fdc3a8a21989af70ef579fa2c" + integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw== + +"@esbuild/darwin-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz#91979d98d30ba6e7d69b22c617cc82bdad60e47a" + integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg== + +"@esbuild/freebsd-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz#b97e97073310736b430a07b099d837084b85e9ce" + integrity sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w== + +"@esbuild/freebsd-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz#f3b694d0da61d9910ec7deff794d444cfbf3b6e7" + integrity sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A== + +"@esbuild/linux-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz#f921f699f162f332036d5657cad9036f7a993f73" + integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg== + +"@esbuild/linux-arm@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz#cc49305b3c6da317c900688995a4050e6cc91ca3" + integrity sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg== + +"@esbuild/linux-ia32@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz#3e0736fcfab16cff042dec806247e2c76e109e19" + integrity sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg== + +"@esbuild/linux-loong64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz#ea2bf730883cddb9dfb85124232b5a875b8020c7" + integrity sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw== + +"@esbuild/linux-mips64el@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz#4cababb14eede09248980a2d2d8b966464294ff1" + integrity sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ== + +"@esbuild/linux-ppc64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz#8860a4609914c065373a77242e985179658e1951" + integrity sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw== + +"@esbuild/linux-riscv64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz#baf26e20bb2d38cfb86ee282dff840c04f4ed987" + integrity sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA== + +"@esbuild/linux-s390x@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz#8323afc0d6cb1b6dc6e9fd21efd9e1542c3640a4" + integrity sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA== + +"@esbuild/linux-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz#08fcf60cb400ed2382e9f8e0f5590bac8810469a" + integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw== + +"@esbuild/netbsd-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz#935c6c74e20f7224918fbe2e6c6fe865b6c6ea5b" + integrity sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw== + +"@esbuild/netbsd-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz#414677cef66d16c5a4d210751eb2881bb9c1b62b" + integrity sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA== + +"@esbuild/openbsd-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz#8fd55a4d08d25cdc572844f13c88d678c84d13f7" + integrity sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw== + +"@esbuild/openbsd-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz#0c48ddb1494bbc2d6bcbaa1429a7f465fa1dedde" + integrity sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg== + +"@esbuild/sunos-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz#86ff9075d77962b60dd26203d7352f92684c8c92" + integrity sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg== + +"@esbuild/win32-arm64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz#849c62327c3229467f5b5cd681bf50588442e96c" + integrity sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw== + +"@esbuild/win32-ia32@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz#f62eb480cd7cca088cb65bb46a6db25b725dc079" + integrity sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA== + +"@esbuild/win32-x64@0.25.0": + version "0.25.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz#c8e119a30a7c8d60b9d2e22d2073722dde3b710b" + integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" @@ -2799,10 +2799,10 @@ dependencies: "@babel/types" "^7.3.0" -"@types/eslint@^8.56.6": - version "8.56.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.6.tgz#d5dc16cac025d313ee101108ba5714ea10eb3ed0" - integrity sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A== +"@types/eslint@^8.56.12": + version "8.56.12" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.12.tgz#1657c814ffeba4d2f84c0d4ba0f44ca7ea1ca53a" + integrity sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -4105,36 +4105,36 @@ es5-ext@0.8.x: resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.8.2.tgz#aba8d9e1943a895ac96837a62a39b3f55ecd94ab" integrity sha512-H19ompyhnKiBdjHR1DPHvf5RHgHPmJaY9JNzFGbMbPgdsUkvnUCN1Ke8J4Y0IMyTwFM2M9l4h2GoHwzwpSmXbA== -esbuild@^0.24.2: - version "0.24.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d" - integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA== +esbuild@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92" + integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw== optionalDependencies: - "@esbuild/aix-ppc64" "0.24.2" - "@esbuild/android-arm" "0.24.2" - "@esbuild/android-arm64" "0.24.2" - "@esbuild/android-x64" "0.24.2" - "@esbuild/darwin-arm64" "0.24.2" - "@esbuild/darwin-x64" "0.24.2" - "@esbuild/freebsd-arm64" "0.24.2" - "@esbuild/freebsd-x64" "0.24.2" - "@esbuild/linux-arm" "0.24.2" - "@esbuild/linux-arm64" "0.24.2" - "@esbuild/linux-ia32" "0.24.2" - "@esbuild/linux-loong64" "0.24.2" - "@esbuild/linux-mips64el" "0.24.2" - "@esbuild/linux-ppc64" "0.24.2" - "@esbuild/linux-riscv64" "0.24.2" - "@esbuild/linux-s390x" "0.24.2" - "@esbuild/linux-x64" "0.24.2" - "@esbuild/netbsd-arm64" "0.24.2" - "@esbuild/netbsd-x64" "0.24.2" - "@esbuild/openbsd-arm64" "0.24.2" - "@esbuild/openbsd-x64" "0.24.2" - "@esbuild/sunos-x64" "0.24.2" - "@esbuild/win32-arm64" "0.24.2" - "@esbuild/win32-ia32" "0.24.2" - "@esbuild/win32-x64" "0.24.2" + "@esbuild/aix-ppc64" "0.25.0" + "@esbuild/android-arm" "0.25.0" + "@esbuild/android-arm64" "0.25.0" + "@esbuild/android-x64" "0.25.0" + "@esbuild/darwin-arm64" "0.25.0" + "@esbuild/darwin-x64" "0.25.0" + "@esbuild/freebsd-arm64" "0.25.0" + "@esbuild/freebsd-x64" "0.25.0" + "@esbuild/linux-arm" "0.25.0" + "@esbuild/linux-arm64" "0.25.0" + "@esbuild/linux-ia32" "0.25.0" + "@esbuild/linux-loong64" "0.25.0" + "@esbuild/linux-mips64el" "0.25.0" + "@esbuild/linux-ppc64" "0.25.0" + "@esbuild/linux-riscv64" "0.25.0" + "@esbuild/linux-s390x" "0.25.0" + "@esbuild/linux-x64" "0.25.0" + "@esbuild/netbsd-arm64" "0.25.0" + "@esbuild/netbsd-x64" "0.25.0" + "@esbuild/openbsd-arm64" "0.25.0" + "@esbuild/openbsd-x64" "0.25.0" + "@esbuild/sunos-x64" "0.25.0" + "@esbuild/win32-arm64" "0.25.0" + "@esbuild/win32-ia32" "0.25.0" + "@esbuild/win32-x64" "0.25.0" escalade@^3.1.1: version "3.1.1" From 5a78dd7cfec4217c1feb89ee6312c13f40b01aed Mon Sep 17 00:00:00 2001 From: lauren Date: Wed, 12 Feb 2025 15:13:47 -0500 Subject: [PATCH 22/73] [ci] Also notify compiler ready for review PRs (#32371) Similar to #32344 --- .github/workflows/compiler_discord_notify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compiler_discord_notify.yml b/.github/workflows/compiler_discord_notify.yml index da85190dd2127..f81f42b9cb7df 100644 --- a/.github/workflows/compiler_discord_notify.yml +++ b/.github/workflows/compiler_discord_notify.yml @@ -2,7 +2,7 @@ name: (Compiler) Discord Notify on: pull_request_target: - types: [opened] + types: [opened, ready_for_review] paths: - compiler/** - .github/workflows/compiler_**.yml From e0131f1edae0fc411bf8abb2fed211ca07af60fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Best?= Date: Thu, 13 Feb 2025 13:02:55 +0100 Subject: [PATCH 23/73] fix(devtools): Handle nullish values passed to `formatConsoleArguments` (#32372) ## Summary When using React Devtools, calling `console.log('%s', null)` in userland can cause it to throw an error: ``` TypeError: Cannot read properties of null (reading 'toString') ``` ## How did you test this change? Added a unit test. See https://github.com/47ng/nuqs/issues/808. --- .../react-devtools-shared/src/__tests__/utils-test.js | 9 +++++++++ .../src/backend/utils/formatConsoleArguments.js | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/__tests__/utils-test.js b/packages/react-devtools-shared/src/__tests__/utils-test.js index 94726312c3bfa..876aaa99f1ea9 100644 --- a/packages/react-devtools-shared/src/__tests__/utils-test.js +++ b/packages/react-devtools-shared/src/__tests__/utils-test.js @@ -470,5 +470,14 @@ function f() { } {}, ]); }); + + it('formats nullish values', () => { + expect(formatConsoleArguments('This is the %s template', null)).toEqual([ + 'This is the null template', + ]); + expect( + formatConsoleArguments('This is the %s template', undefined), + ).toEqual(['This is the undefined template']); + }); }); }); diff --git a/packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js b/packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js index a2d303e543ac0..eaf4170970ed4 100644 --- a/packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js +++ b/packages/react-devtools-shared/src/backend/utils/formatConsoleArguments.js @@ -58,7 +58,7 @@ export default function formatConsoleArguments( } case 's': { const [arg] = args.splice(argumentsPointer, 1); - template += arg.toString(); + template += String(arg); break; } From cbbe8666a8d6e6f1b81dffb11bd5d767e4acd6ac Mon Sep 17 00:00:00 2001 From: LoganDark <4723091+LoganDark@users.noreply.github.com> Date: Thu, 13 Feb 2025 04:04:53 -0800 Subject: [PATCH 24/73] fix value formatting of proxies of class instances (#30880) For Hookstate Proxies of class instances, `data.constructor.name` returns `Proxy({})`, so use `Object.getPrototypeOf(data).constructor.name` instead, which works correctly from my testing. ## Summary React DevTools immediately bricks itself if you inspect any component that has a prop that is a Hookstate that wraps a class instance ... because these are proxies where `data.constructor.name` returns some un-cloneable object, but `Object.getPrototypeOf(data)` doesn't return `Object` (it returns the prototype of the class inside). ## How did you test this change? This part of the code has no associated tests at all. Technically, `packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js` exists, but I tried `yarn test` and these tests aren't even executed anymore. I can't figure it out, so whatever. If you run this code: ```js class Class {} const instance = new Class(); const instanceProxy = new Proxy(instance, { get(target, key, receiver) { if (key === 'constructor') { return { name: new Proxy({}, {}) }; } return Reflect.get(target, key, receiver); }, }); ``` then `instanceProxy.constructor.name` returns some proxy that cannot be cloned, but `Object.getPrototypeOf(instanceProxy).constructor.name` returns the correct value. This PR fixes the devtools to use `Object.getPrototypeOf(instanceProxy).constructor.name`. I modified my local copy of devtools to use this method and it fixed the bricking that I experienced. Related #29954 --- packages/react-devtools-shared/src/utils.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index f6f7f3f4209aa..12c9fb739ce95 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -915,7 +915,25 @@ export function formatDataForPreview( case 'date': return data.toString(); case 'class_instance': - return data.constructor.name; + try { + let resolvedConstructorName = data.constructor.name; + if (typeof resolvedConstructorName === 'string') { + return resolvedConstructorName; + } + + resolvedConstructorName = Object.getPrototypeOf(data).constructor.name; + if (typeof resolvedConstructorName === 'string') { + return resolvedConstructorName; + } + + try { + return truncateForDisplay(String(data)); + } catch (error) { + return 'unserializable'; + } + } catch (error) { + return 'unserializable'; + } case 'object': if (showFormattedValue) { const keys = Array.from(getAllEnumerableKeys(data)).sort(alphaSortKeys); From c6a7e18636e610efd3aa7a437bbcaf321bf73abd Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Thu, 13 Feb 2025 18:09:49 +0100 Subject: [PATCH 25/73] Ensure `captureOwnerStack` returns `null` when no stack is available (#32353) Co-authored-by: Younes Henni --- packages/react-reconciler/src/ReactCurrentFiber.js | 2 +- .../src/__tests__/ReactOwnerStacks-test.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactCurrentFiber.js b/packages/react-reconciler/src/ReactCurrentFiber.js index 18406d991909a..f65ae5f19232c 100644 --- a/packages/react-reconciler/src/ReactCurrentFiber.js +++ b/packages/react-reconciler/src/ReactCurrentFiber.js @@ -72,7 +72,7 @@ export function runWithFiberInDEV( } return callback(arg0, arg1, arg2, arg3, arg4); } finally { - current = previousFiber; + setCurrentFiber(previousFiber); } } // These errors should never make it into a build so we don't need to encode them in codes.json diff --git a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js index ca0e9fd12cc11..7ee176a106e15 100644 --- a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js @@ -62,4 +62,17 @@ describe('ReactOwnerStacks', () => { '\n in Bar (at **)' + '\n in Foo (at **)', ); }); + + it('returns null outside of render', async () => { + // Awkward to gate since some builds will have `captureOwnerStack` return null in prod + if (__DEV__ && gate('enableOwnerStacks')) { + expect(React.captureOwnerStack()).toBe(null); + + await act(() => { + ReactNoop.render(
); + }); + + expect(React.captureOwnerStack()).toBe(null); + } + }); }); From ed8b68dd178af17a2dd36c8678f81f8b454559a9 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Thu, 13 Feb 2025 18:26:36 +0100 Subject: [PATCH 26/73] Stop exporting dev-only methods in OSS production builds (#32200) --- .../src/__tests__/ReactTestUtilsAct-test.js | 8 --- .../src/__tests__/ReactIsomorphicAct-test.js | 12 +++++ .../src/__tests__/ReactOwnerStacks-test.js | 10 ++++ packages/react/index.js | 2 - packages/react/index.stable.development.js | 51 +++++++++++++++++++ packages/react/index.stable.js | 1 - scripts/jest/TestFlags.js | 1 + scripts/jest/setupHostConfigs.js | 19 ++++--- scripts/rollup/build.js | 21 ++++---- 9 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 packages/react/index.stable.development.js diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js index 76cfab25e0016..64ccc387d75a3 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js @@ -779,13 +779,5 @@ function runActTests(render, unmount, rerender) { }); } }); - describe('throw in prod mode', () => { - // @gate !__DEV__ - it('warns if you try to use act() in prod mode', () => { - expect(() => act(() => {})).toThrow( - 'act(...) is not supported in production builds of React', - ); - }); - }); }); } diff --git a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js index eeec3b2b7eb3b..874969b131ea5 100644 --- a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js @@ -50,6 +50,18 @@ describe('isomorphic act()', () => { return text; } + it('behavior in production', () => { + if (!__DEV__) { + if (gate('fb')) { + expect(() => act(() => {})).toThrow( + 'act(...) is not supported in production builds of React', + ); + } else { + expect(React).not.toHaveProperty('act'); + } + } + }); + // @gate __DEV__ it('bypasses queueMicrotask', async () => { const root = ReactNoop.createRoot(); diff --git a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js index 7ee176a106e15..42294269e7b14 100644 --- a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js @@ -31,6 +31,16 @@ describe('ReactOwnerStacks', () => { ); } + it('behavior in production', () => { + if (!__DEV__) { + if (gate('fb')) { + expect(React).toHaveProperty('captureOwnerStack', undefined); + } else { + expect(React).not.toHaveProperty('captureOwnerStack'); + } + } + }); + // @gate __DEV__ && enableOwnerStacks it('can get the component owner stacks during rendering in dev', async () => { let stack; diff --git a/packages/react/index.js b/packages/react/index.js index 711a044455257..af4cd4b0c1b17 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -23,8 +23,6 @@ export type ElementRef = React$ElementRef; export type Config = React$Config; export type ChildrenArray<+T> = $ReadOnlyArray> | T; -// Export all exports so that they're available in tests. -// We can't use export * from in Flow for some reason. export { __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, __COMPILER_RUNTIME, diff --git a/packages/react/index.stable.development.js b/packages/react/index.stable.development.js new file mode 100644 index 0000000000000..f9ac98bafbf58 --- /dev/null +++ b/packages/react/index.stable.development.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export { + __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, + __COMPILER_RUNTIME, + Children, + Component, + Fragment, + Profiler, + PureComponent, + StrictMode, + Suspense, + cloneElement, + createContext, + createElement, + createRef, + use, + forwardRef, + isValidElement, + lazy, + memo, + cache, + unstable_useCacheRefresh, + startTransition, + useId, + useCallback, + useContext, + useDebugValue, + useDeferredValue, + useEffect, + useImperativeHandle, + useInsertionEffect, + useLayoutEffect, + useMemo, + useReducer, + useOptimistic, + useRef, + useState, + useSyncExternalStore, + useTransition, + useActionState, + version, + act, // DEV-only +} from './src/ReactClient'; diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js index 26e22c343223e..6f25c7a37d983 100644 --- a/packages/react/index.stable.js +++ b/packages/react/index.stable.js @@ -10,7 +10,6 @@ export { __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, __COMPILER_RUNTIME, - act, Children, Component, Fragment, diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js index b970955491aae..f736ca2254f64 100644 --- a/scripts/jest/TestFlags.js +++ b/scripts/jest/TestFlags.js @@ -78,6 +78,7 @@ function getTestFlags() { classic: releaseChannel === 'classic', source: !process.env.IS_BUILD, www, + fb: www || xplat, // These aren't flags, just a useful aliases for tests. enableActivity: releaseChannel === 'experimental' || www || xplat, diff --git a/scripts/jest/setupHostConfigs.js b/scripts/jest/setupHostConfigs.js index b52fac3201ebf..aacfaf116fcd2 100644 --- a/scripts/jest/setupHostConfigs.js +++ b/scripts/jest/setupHostConfigs.js @@ -43,9 +43,9 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { } resolvedEntry = nodePath.join(resolvedEntry, '..', entrypoint); - const developmentEntry = resolvedEntry.replace('.js', '.development.js'); - if (fs.existsSync(developmentEntry)) { - return developmentEntry; + const devEntry = resolvedEntry.replace('.js', '.development.js'); + if (__DEV__ && fs.existsSync(devEntry)) { + return devEntry; } if (fs.existsSync(resolvedEntry)) { return resolvedEntry; @@ -60,13 +60,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js' ); const devFBEntry = resolvedFBEntry.replace('.js', '.development.js'); - if (fs.existsSync(devFBEntry)) { + if (__DEV__ && fs.existsSync(devFBEntry)) { return devFBEntry; } if (fs.existsSync(resolvedFBEntry)) { return resolvedFBEntry; } const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js'); + const devGenericFBEntry = resolvedGenericFBEntry.replace( + '.js', + '.development.js' + ); + if (__DEV__ && fs.existsSync(devGenericFBEntry)) { + return devGenericFBEntry; + } if (fs.existsSync(resolvedGenericFBEntry)) { return resolvedGenericFBEntry; } @@ -77,14 +84,14 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js' ); const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js'); - if (fs.existsSync(devForkedEntry)) { + if (__DEV__ && fs.existsSync(devForkedEntry)) { return devForkedEntry; } if (fs.existsSync(resolvedForkedEntry)) { return resolvedForkedEntry; } const plainDevEntry = resolvedEntry.replace('.js', '.development.js'); - if (fs.existsSync(plainDevEntry)) { + if (__DEV__ && fs.existsSync(plainDevEntry)) { return plainDevEntry; } // Just use the plain .js one. diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 2a67c13e1e620..547362dae7123 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -571,7 +571,7 @@ function shouldSkipBundle(bundle, bundleType) { return false; } -function resolveEntryFork(resolvedEntry, isFBBundle) { +function resolveEntryFork(resolvedEntry, isFBBundle, isDev) { // Pick which entry point fork to use: // .modern.fb.js // .classic.fb.js @@ -586,23 +586,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { '.js', __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js' ); - const developmentFBEntry = resolvedFBEntry.replace( - '.js', - '.development.js' - ); - if (fs.existsSync(developmentFBEntry)) { - return developmentFBEntry; + const devFBEntry = resolvedFBEntry.replace('.js', '.development.js'); + if (isDev && fs.existsSync(devFBEntry)) { + return devFBEntry; } if (fs.existsSync(resolvedFBEntry)) { return resolvedFBEntry; } const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js'); - const developmentGenericFBEntry = resolvedGenericFBEntry.replace( + const devGenericFBEntry = resolvedGenericFBEntry.replace( '.js', '.development.js' ); - if (fs.existsSync(developmentGenericFBEntry)) { - return developmentGenericFBEntry; + if (isDev && fs.existsSync(devGenericFBEntry)) { + return devGenericFBEntry; } if (fs.existsSync(resolvedGenericFBEntry)) { return resolvedGenericFBEntry; @@ -614,7 +611,7 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js' ); const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js'); - if (fs.existsSync(devForkedEntry)) { + if (isDev && fs.existsSync(devForkedEntry)) { return devForkedEntry; } if (fs.existsSync(resolvedForkedEntry)) { @@ -633,7 +630,7 @@ async function createBundle(bundle, bundleType) { const {isFBWWWBundle, isFBRNBundle} = getBundleTypeFlags(bundleType); - let resolvedEntry = resolveEntryFork( + const resolvedEntry = resolveEntryFork( require.resolve(bundle.entry), isFBWWWBundle || isFBRNBundle, !isProductionBundleType(bundleType) From 32b0cad8f74da3d6e8b07f4ffbad26dfe8d8a71a Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Thu, 13 Feb 2025 20:38:57 +0100 Subject: [PATCH 27/73] Enable owner stacks in Canary builds (#32053) Pending internal decision to ship in Canary. Still off for FB builds. Docs: https://github.com/reactjs/react.dev/pull/7427 --- packages/react/index.stable.development.js | 1 + packages/react/src/ReactServer.js | 2 ++ packages/shared/ReactFeatureFlags.js | 2 +- packages/shared/forks/ReactFeatureFlags.test-renderer.js | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/react/index.stable.development.js b/packages/react/index.stable.development.js index f9ac98bafbf58..2397010cf5604 100644 --- a/packages/react/index.stable.development.js +++ b/packages/react/index.stable.development.js @@ -48,4 +48,5 @@ export { useActionState, version, act, // DEV-only + captureOwnerStack, // DEV-only } from './src/ReactClient'; diff --git a/packages/react/src/ReactServer.js b/packages/react/src/ReactServer.js index d6702023e4741..a00e192bd3c1d 100644 --- a/packages/react/src/ReactServer.js +++ b/packages/react/src/ReactServer.js @@ -28,6 +28,7 @@ import {lazy} from './ReactLazy'; import {memo} from './ReactMemo'; import {cache} from './ReactCacheServer'; import version from 'shared/ReactVersion'; +import {captureOwnerStack} from './ReactOwnerStack'; const Children = { map, @@ -57,4 +58,5 @@ export { useDebugValue, useMemo, version, + captureOwnerStack, // DEV-only }; diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index fbdfe5a468516..844b2a2f6a914 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -127,7 +127,7 @@ export const passChildrenWhenCloningPersistedNodes = false; */ export const enablePersistedModeClonedFlag = false; -export const enableOwnerStacks = __EXPERIMENTAL__; +export const enableOwnerStacks = true; export const enableShallowPropDiffing = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 6fcd35c40774b..ef3c300602bdb 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -88,7 +88,7 @@ export const enableReactTestRendererWarning = true; export const disableDefaultPropsExceptForClasses = true; export const enableObjectFiber = false; -export const enableOwnerStacks = false; +export const enableOwnerStacks = true; export const enableRemoveConsolePatches = true; // Flow magic to verify the exports of this file match the original version. From a53da6abe1593483098df2baf927fe07d80153a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 13 Feb 2025 16:06:01 -0500 Subject: [PATCH 28/73] Add useSwipeTransition Hook Behind Experimental Flag (#32373) This Hook will be used to drive a View Transition based on a gesture. ```js const [value, startGesture] = useSwipeTransition(prev, current, next); ``` The `enableSwipeTransition` flag will depend on `enableViewTransition` flag but we may decide to ship them independently. This PR doesn't do anything interesting yet. There will be a lot more PRs to build out the actual functionality. This is just wiring up the plumbing for the new Hook. This first PR is mainly concerned with how the whole starts (and stops). The core API is the `startGesture` function (although there will be other conveniences added in the future). You can call this to start a gesture with a source provider. You can call this multiple times in one event to batch multiple Hooks listening to the same provider. However, each render can only handle one source provider at a time and so it does one render per scheduled gesture provider. This uses a separate `GestureLane` to drive gesture renders by marking the Hook as having an update on that lane. Then schedule a render. These renders should be blocking and in the same microtask as the `startGesture` to ensure it can block the paint. So it's similar to sync. It may not be possible to finish it synchronously e.g. if something suspends. If so, it just tries again later when it can like any other render. This can also happen because it also may not be possible to drive more than one gesture at a time like if we're limited to one View Transition per document. So right now you can only run one gesture at a time in practice. These renders never commit. This means that we can't clear the `GestureLane` the normal way. Instead, we have to clear only the root's `pendingLanes` if we don't have any new renders scheduled. Then wait until something else updates the Fiber after all gestures on it have stopped before it really clears. --- .../view-transition/src/components/Page.css | 11 + .../view-transition/src/components/Page.js | 39 ++- .../react-debug-tools/src/ReactDebugHooks.js | 37 ++- .../src/ReactFiberConcurrentUpdates.js | 38 ++- .../src/ReactFiberGestureScheduler.js | 90 +++++++ .../react-reconciler/src/ReactFiberHooks.js | 242 ++++++++++++++++++ .../react-reconciler/src/ReactFiberLane.js | 52 ++-- .../react-reconciler/src/ReactFiberRoot.js | 5 + .../src/ReactFiberRootScheduler.js | 8 +- .../src/ReactFiberWorkLoop.js | 44 ++++ .../src/ReactInternalTypes.js | 14 +- packages/react-server/src/ReactFizzHooks.js | 42 ++- packages/react-server/src/ReactFlightHooks.js | 45 ++-- .../react/index.experimental.development.js | 1 + packages/react/index.experimental.js | 1 + packages/react/src/ReactClient.js | 8 +- packages/react/src/ReactHooks.js | 19 +- packages/shared/ReactFeatureFlags.js | 2 + packages/shared/ReactTypes.js | 6 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + ...actFeatureFlags.test-renderer.native-fb.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 2 + scripts/error-codes/codes.json | 4 +- 26 files changed, 647 insertions(+), 68 deletions(-) create mode 100644 packages/react-reconciler/src/ReactFiberGestureScheduler.js diff --git a/fixtures/view-transition/src/components/Page.css b/fixtures/view-transition/src/components/Page.css index 5afcc33a8f4bc..a5a9373194ef8 100644 --- a/fixtures/view-transition/src/components/Page.css +++ b/fixtures/view-transition/src/components/Page.css @@ -6,3 +6,14 @@ font-variation-settings: "wdth" 100; } + +.swipe-recognizer { + width: 200px; + overflow-x: scroll; + border: 1px solid #333333; + border-radius: 10px; +} + +.swipe-overscroll { + width: 200%; +} diff --git a/fixtures/view-transition/src/components/Page.js b/fixtures/view-transition/src/components/Page.js index 40f60327728fd..1c7a9bfeb32db 100644 --- a/fixtures/view-transition/src/components/Page.js +++ b/fixtures/view-transition/src/components/Page.js @@ -1,6 +1,9 @@ import React, { unstable_ViewTransition as ViewTransition, unstable_Activity as Activity, + unstable_useSwipeTransition as useSwipeTransition, + useRef, + useLayoutEffect, } from 'react'; import './Page.css'; @@ -35,7 +38,8 @@ function Component() { } export default function Page({url, navigate}) { - const show = url === '/?b'; + const [renderedUrl, startGesture] = useSwipeTransition('/?a', url, '/?b'); + const show = renderedUrl === '/?b'; function onTransition(viewTransition, types) { const keyframes = [ {rotate: '0deg', transformOrigin: '30px 8px'}, @@ -44,6 +48,32 @@ export default function Page({url, navigate}) { viewTransition.old.animate(keyframes, 250); viewTransition.new.animate(keyframes, 250); } + + const swipeRecognizer = useRef(null); + const activeGesture = useRef(null); + function onScroll() { + if (activeGesture.current !== null) { + return; + } + // eslint-disable-next-line no-undef + const scrollTimeline = new ScrollTimeline({ + source: swipeRecognizer.current, + axis: 'x', + }); + activeGesture.current = startGesture(scrollTimeline); + } + function onScrollEnd() { + if (activeGesture.current !== null) { + const cancelGesture = activeGesture.current; + activeGesture.current = null; + cancelGesture(); + } + } + + useLayoutEffect(() => { + swipeRecognizer.current.scrollLeft = show ? 0 : 10000; + }, [show]); + const exclamation = ( ! @@ -90,6 +120,13 @@ export default function Page({url, navigate}) {

+
+
Swipe me
+

{show ? null : ( diff --git a/packages/react-debug-tools/src/ReactDebugHooks.js b/packages/react-debug-tools/src/ReactDebugHooks.js index 114080d03e921..798cdec4380f8 100644 --- a/packages/react-debug-tools/src/ReactDebugHooks.js +++ b/packages/react-debug-tools/src/ReactDebugHooks.js @@ -14,6 +14,7 @@ import type { Usable, Thenable, ReactDebugInfo, + StartGesture, } from 'shared/ReactTypes'; import type { ContextDependency, @@ -131,6 +132,9 @@ function getPrimitiveStackCache(): Map> { if (typeof Dispatcher.useEffectEvent === 'function') { Dispatcher.useEffectEvent((args: empty) => {}); } + if (typeof Dispatcher.useSwipeTransition === 'function') { + Dispatcher.useSwipeTransition(null, null, null); + } } finally { readHookLog = hookLog; hookLog = []; @@ -752,31 +756,50 @@ function useEffectEvent) => mixed>(callback: F): F { return callback; } +function useSwipeTransition( + previous: T, + current: T, + next: T, +): [T, StartGesture] { + nextHook(); + hookLog.push({ + displayName: null, + primitive: 'SwipeTransition', + stackError: new Error(), + value: current, + debugInfo: null, + dispatcherHookName: 'SwipeTransition', + }); + return [current, () => () => {}]; +} + const Dispatcher: DispatcherType = { - use, readContext, - useCacheRefresh, + + use, useCallback, useContext, useEffect, useImperativeHandle, - useDebugValue, useLayoutEffect, useInsertionEffect, useMemo, - useMemoCache, - useOptimistic, useReducer, useRef, useState, + useDebugValue, + useDeferredValue, useTransition, useSyncExternalStore, - useDeferredValue, useId, + useHostTransitionStatus, useFormState, useActionState, - useHostTransitionStatus, + useOptimistic, + useMemoCache, + useCacheRefresh, useEffectEvent, + useSwipeTransition, }; // create a proxy to throw a custom error diff --git a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js index 5413db10f6f75..859e553ccbcb6 100644 --- a/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js +++ b/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js @@ -24,7 +24,14 @@ import { throwIfInfiniteUpdateLoopDetected, getWorkInProgressRoot, } from './ReactFiberWorkLoop'; -import {NoLane, NoLanes, mergeLanes, markHiddenUpdate} from './ReactFiberLane'; +import { + NoLane, + NoLanes, + mergeLanes, + markHiddenUpdate, + markRootUpdated, + GestureLane, +} from './ReactFiberLane'; import {NoFlags, Placement, Hydrating} from './ReactFiberFlags'; import {HostRoot, OffscreenComponent} from './ReactWorkTags'; import {OffscreenVisible} from './ReactFiberActivityComponent'; @@ -169,6 +176,25 @@ export function enqueueConcurrentRenderForLane( return getRootForUpdatedFiber(fiber); } +export function enqueueGestureRender(fiber: Fiber): FiberRoot | null { + // We can't use the concurrent queuing for these so this is basically just a + // short cut for marking the lane on the parent path. It is possible for a + // gesture render to suspend and then in the gap get another gesture starting. + // However, marking the lane doesn't make much different in this case because + // it would have to call startGesture with the same exact provider as was + // already rendering. Because otherwise it has no effect on the Hook itself. + // TODO: We could potentially solve this case by popping a ScheduledGesture + // off the root's queue while we're rendering it so that it can't dedupe + // and so new startGesture with the same provider would create a new + // ScheduledGesture which goes into a separate render pass anyway. + // This is such an edge case it probably doesn't matter much. + const root = markUpdateLaneFromFiberToRoot(fiber, null, GestureLane); + if (root !== null) { + markRootUpdated(root, GestureLane); + } + return root; +} + // Calling this function outside this module should only be done for backwards // compatibility and should always be accompanied by a warning. export function unsafe_markUpdateLaneFromFiberToRoot( @@ -189,7 +215,7 @@ function markUpdateLaneFromFiberToRoot( sourceFiber: Fiber, update: ConcurrentUpdate | null, lane: Lane, -): void { +): null | FiberRoot { // Update the source fiber's lanes sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); let alternate = sourceFiber.alternate; @@ -238,10 +264,14 @@ function markUpdateLaneFromFiberToRoot( parent = parent.return; } - if (isHidden && update !== null && node.tag === HostRoot) { + if (node.tag === HostRoot) { const root: FiberRoot = node.stateNode; - markHiddenUpdate(root, update, lane); + if (isHidden && update !== null) { + markHiddenUpdate(root, update, lane); + } + return root; } + return null; } function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null { diff --git a/packages/react-reconciler/src/ReactFiberGestureScheduler.js b/packages/react-reconciler/src/ReactFiberGestureScheduler.js new file mode 100644 index 0000000000000..7664316b67558 --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberGestureScheduler.js @@ -0,0 +1,90 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {FiberRoot} from './ReactInternalTypes'; +import type {GestureProvider} from 'shared/ReactTypes'; + +import {GestureLane} from './ReactFiberLane'; +import {ensureRootIsScheduled} from './ReactFiberRootScheduler'; + +// This type keeps track of any scheduled or active gestures. +export type ScheduledGesture = { + provider: GestureProvider, + count: number, // The number of times this same provider has been started. + prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root. + next: null | ScheduledGesture, // The next scheduled gesture in the queue for this root. +}; + +export function scheduleGesture( + root: FiberRoot, + provider: GestureProvider, +): ScheduledGesture { + let prev = root.gestures; + while (prev !== null) { + if (prev.provider === provider) { + // Existing instance found. + prev.count++; + return prev; + } + const next = prev.next; + if (next === null) { + break; + } + prev = next; + } + // Add new instance to the end of the queue. + const gesture: ScheduledGesture = { + provider: provider, + count: 1, + prev: prev, + next: null, + }; + if (prev === null) { + root.gestures = gesture; + } else { + prev.next = gesture; + } + ensureRootIsScheduled(root); + return gesture; +} + +export function cancelScheduledGesture( + root: FiberRoot, + gesture: ScheduledGesture, +): void { + gesture.count--; + if (gesture.count === 0) { + // Delete the scheduled gesture from the queue. + deleteScheduledGesture(root, gesture); + } +} + +export function deleteScheduledGesture( + root: FiberRoot, + gesture: ScheduledGesture, +): void { + if (gesture.prev === null) { + if (root.gestures === gesture) { + root.gestures = gesture.next; + if (root.gestures === null) { + // Gestures don't clear their lanes while the gesture is still active but it + // might not be scheduled to do any more renders and so we shouldn't schedule + // any more gesture lane work until a new gesture is scheduled. + root.pendingLanes &= ~GestureLane; + } + } + } else { + gesture.prev.next = gesture.next; + if (gesture.next !== null) { + gesture.next.prev = gesture.prev; + } + gesture.prev = null; + gesture.next = null; + } +} diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 2ab41770bd0ac..06e00441c3b8e 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -14,6 +14,8 @@ import type { Thenable, RejectedThenable, Awaited, + StartGesture, + GestureProvider, } from 'shared/ReactTypes'; import type { Fiber, @@ -26,6 +28,7 @@ import type {Lanes, Lane} from './ReactFiberLane'; import type {HookFlags} from './ReactHookEffectTags'; import type {Flags} from './ReactFiberFlags'; import type {TransitionStatus} from './ReactFiberConfig'; +import type {ScheduledGesture} from './ReactFiberGestureScheduler'; import { HostTransitionContext, @@ -42,6 +45,7 @@ import { enableLegacyCache, disableLegacyMode, enableNoCloningMemoCache, + enableSwipeTransition, } from 'shared/ReactFeatureFlags'; import { REACT_CONTEXT_TYPE, @@ -70,6 +74,8 @@ import { isTransitionLane, markRootEntangled, includesSomeLane, + isGestureRender, + GestureLane, } from './ReactFiberLane'; import { ContinuousEventPriority, @@ -130,6 +136,7 @@ import { enqueueConcurrentHookUpdate, enqueueConcurrentHookUpdateAndEagerlyBailout, enqueueConcurrentRenderForLane, + enqueueGestureRender, } from './ReactFiberConcurrentUpdates'; import {getTreeId} from './ReactFiberTreeContext'; import {now} from './Scheduler'; @@ -153,6 +160,11 @@ import {requestCurrentTransition} from './ReactFiberTransition'; import {callComponentInDEV} from './ReactFiberCallUserSpace'; +import { + scheduleGesture, + cancelScheduledGesture, +} from './ReactFiberGestureScheduler'; + export type Update = { lane: Lane, revertLane: Lane, @@ -3960,6 +3972,133 @@ function markUpdateInDevTools
(fiber: Fiber, lane: Lane, action: A): void { } } +type SwipeTransitionGestureUpdate = { + gesture: ScheduledGesture, + prev: SwipeTransitionGestureUpdate | null, + next: SwipeTransitionGestureUpdate | null, +}; + +type SwipeTransitionUpdateQueue = { + pending: null | SwipeTransitionGestureUpdate, + dispatch: StartGesture, +}; + +function startGesture( + fiber: Fiber, + queue: SwipeTransitionUpdateQueue, + gestureProvider: GestureProvider, +): () => void { + const root = enqueueGestureRender(fiber); + if (root === null) { + // Already unmounted. + // TODO: Should we warn here about starting on an unmounted Fiber? + return function cancelGesture() { + // Noop. + }; + } + const scheduledGesture = scheduleGesture(root, gestureProvider); + // Add this particular instance to the queue. + // We add multiple of the same provider even if they get batched so + // that if we cancel one but not the other we can keep track of this. + // Order doesn't matter but we insert in the beginning to avoid two fields. + const update: SwipeTransitionGestureUpdate = { + gesture: scheduledGesture, + prev: null, + next: queue.pending, + }; + if (queue.pending !== null) { + queue.pending.prev = update; + } + queue.pending = update; + return function cancelGesture(): void { + if (update.prev === null) { + if (queue.pending === update) { + queue.pending = update.next; + } else { + // This was already cancelled. Avoid double decrementing if someone calls this twice by accident. + // TODO: Should we warn here about double cancelling? + return; + } + } else { + update.prev.next = update.next; + if (update.next !== null) { + update.next.prev = update.prev; + } + update.prev = null; + update.next = null; + } + const cancelledGestured = update.gesture; + // Decrement ref count of the root schedule. + cancelScheduledGesture(root, cancelledGestured); + }; +} + +function mountSwipeTransition( + previous: T, + current: T, + next: T, +): [T, StartGesture] { + const queue: SwipeTransitionUpdateQueue = { + pending: null, + dispatch: (null: any), + }; + const startGestureOnHook: StartGesture = (queue.dispatch = (startGesture.bind( + null, + currentlyRenderingFiber, + queue, + ): any)); + const hook = mountWorkInProgressHook(); + hook.queue = queue; + return [current, startGestureOnHook]; +} + +function updateSwipeTransition( + previous: T, + current: T, + next: T, +): [T, StartGesture] { + const hook = updateWorkInProgressHook(); + const queue: SwipeTransitionUpdateQueue = hook.queue; + const startGestureOnHook: StartGesture = queue.dispatch; + const rootRenderLanes = getWorkInProgressRootRenderLanes(); + let value = current; + if (isGestureRender(rootRenderLanes)) { + // We're inside a gesture render. We'll traverse the queue to see if + // this specific Hook is part of this gesture and, if so, which + // direction to render. + const root: FiberRoot | null = getWorkInProgressRoot(); + if (root === null) { + throw new Error( + 'Expected a work-in-progress root. This is a bug in React. Please file an issue.', + ); + } + // We assume that the currently rendering gesture is the one first in the queue. + const rootRenderGesture = root.gestures; + let update = queue.pending; + while (update !== null) { + if (rootRenderGesture === update.gesture) { + // We had a match, meaning we're currently rendering a direction of this + // hook for this gesture. + // TODO: Determine which direction this gesture is currently rendering. + value = previous; + break; + } + update = update.next; + } + } + if (queue.pending !== null) { + // As long as there are any active gestures we need to leave the lane on + // in case we need to render it later. Since a gesture render doesn't commit + // the only time it really fully gets cleared is if something else rerenders + // this component after all the active gestures has cleared. + currentlyRenderingFiber.lanes = mergeLanes( + currentlyRenderingFiber.lanes, + GestureLane, + ); + } + return [value, startGestureOnHook]; +} + export const ContextOnlyDispatcher: Dispatcher = { readContext, @@ -3989,6 +4128,10 @@ export const ContextOnlyDispatcher: Dispatcher = { if (enableUseEffectEventHook) { (ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError; } +if (enableSwipeTransition) { + (ContextOnlyDispatcher: Dispatcher).useSwipeTransition = + throwInvalidHookError; +} const HooksDispatcherOnMount: Dispatcher = { readContext, @@ -4019,6 +4162,10 @@ const HooksDispatcherOnMount: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent; } +if (enableSwipeTransition) { + (HooksDispatcherOnMount: Dispatcher).useSwipeTransition = + mountSwipeTransition; +} const HooksDispatcherOnUpdate: Dispatcher = { readContext, @@ -4049,6 +4196,10 @@ const HooksDispatcherOnUpdate: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent; } +if (enableSwipeTransition) { + (HooksDispatcherOnUpdate: Dispatcher).useSwipeTransition = + updateSwipeTransition; +} const HooksDispatcherOnRerender: Dispatcher = { readContext, @@ -4079,6 +4230,10 @@ const HooksDispatcherOnRerender: Dispatcher = { if (enableUseEffectEventHook) { (HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent; } +if (enableSwipeTransition) { + (HooksDispatcherOnRerender: Dispatcher).useSwipeTransition = + updateSwipeTransition; +} let HooksDispatcherOnMountInDEV: Dispatcher | null = null; let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null; @@ -4296,6 +4451,18 @@ if (__DEV__) { return mountEvent(callback); }; } + if (enableSwipeTransition) { + (HooksDispatcherOnMountInDEV: Dispatcher).useSwipeTransition = + function useSwipeTransition( + previous: T, + current: T, + next: T, + ): [T, StartGesture] { + currentHookNameInDev = 'useSwipeTransition'; + mountHookTypesDev(); + return mountSwipeTransition(previous, current, next); + }; + } HooksDispatcherOnMountWithHookTypesInDEV = { readContext(context: ReactContext): T { @@ -4479,6 +4646,18 @@ if (__DEV__) { return mountEvent(callback); }; } + if (enableSwipeTransition) { + (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useSwipeTransition = + function useSwipeTransition( + previous: T, + current: T, + next: T, + ): [T, StartGesture] { + currentHookNameInDev = 'useSwipeTransition'; + updateHookTypesDev(); + return updateSwipeTransition(previous, current, next); + }; + } HooksDispatcherOnUpdateInDEV = { readContext(context: ReactContext): T { @@ -4662,6 +4841,18 @@ if (__DEV__) { return updateEvent(callback); }; } + if (enableSwipeTransition) { + (HooksDispatcherOnUpdateInDEV: Dispatcher).useSwipeTransition = + function useSwipeTransition( + previous: T, + current: T, + next: T, + ): [T, StartGesture] { + currentHookNameInDev = 'useSwipeTransition'; + updateHookTypesDev(); + return updateSwipeTransition(previous, current, next); + }; + } HooksDispatcherOnRerenderInDEV = { readContext(context: ReactContext): T { @@ -4845,6 +5036,18 @@ if (__DEV__) { return updateEvent(callback); }; } + if (enableSwipeTransition) { + (HooksDispatcherOnRerenderInDEV: Dispatcher).useSwipeTransition = + function useSwipeTransition( + previous: T, + current: T, + next: T, + ): [T, StartGesture] { + currentHookNameInDev = 'useSwipeTransition'; + updateHookTypesDev(); + return updateSwipeTransition(previous, current, next); + }; + } InvalidNestedHooksDispatcherOnMountInDEV = { readContext(context: ReactContext): T { @@ -5053,6 +5256,19 @@ if (__DEV__) { return mountEvent(callback); }; } + if (enableSwipeTransition) { + (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useSwipeTransition = + function useSwipeTransition( + previous: T, + current: T, + next: T, + ): [T, StartGesture] { + currentHookNameInDev = 'useSwipeTransition'; + warnInvalidHookAccess(); + mountHookTypesDev(); + return mountSwipeTransition(previous, current, next); + }; + } InvalidNestedHooksDispatcherOnUpdateInDEV = { readContext(context: ReactContext): T { @@ -5261,6 +5477,19 @@ if (__DEV__) { return updateEvent(callback); }; } + if (enableSwipeTransition) { + (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useSwipeTransition = + function useSwipeTransition( + previous: T, + current: T, + next: T, + ): [T, StartGesture] { + currentHookNameInDev = 'useSwipeTransition'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateSwipeTransition(previous, current, next); + }; + } InvalidNestedHooksDispatcherOnRerenderInDEV = { readContext(context: ReactContext): T { @@ -5469,4 +5698,17 @@ if (__DEV__) { return updateEvent(callback); }; } + if (enableSwipeTransition) { + (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useSwipeTransition = + function useSwipeTransition( + previous: T, + current: T, + next: T, + ): [T, StartGesture] { + currentHookNameInDev = 'useSwipeTransition'; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateSwipeTransition(previous, current, next); + }; + } } diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 922fe2f977d45..22bf99624dbb4 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -54,23 +54,24 @@ export const DefaultLane: Lane = /* */ 0b0000000000000000000 export const SyncUpdateLanes: Lane = SyncLane | InputContinuousLane | DefaultLane; -const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000; -const TransitionLanes: Lanes = /* */ 0b0000000001111111111111110000000; -const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000; -const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000; -const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000; -const TransitionLane4: Lane = /* */ 0b0000000000000000000010000000000; -const TransitionLane5: Lane = /* */ 0b0000000000000000000100000000000; -const TransitionLane6: Lane = /* */ 0b0000000000000000001000000000000; -const TransitionLane7: Lane = /* */ 0b0000000000000000010000000000000; -const TransitionLane8: Lane = /* */ 0b0000000000000000100000000000000; -const TransitionLane9: Lane = /* */ 0b0000000000000001000000000000000; -const TransitionLane10: Lane = /* */ 0b0000000000000010000000000000000; -const TransitionLane11: Lane = /* */ 0b0000000000000100000000000000000; -const TransitionLane12: Lane = /* */ 0b0000000000001000000000000000000; -const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000; -const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000; -const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000; +export const GestureLane: Lane = /* */ 0b0000000000000000000000001000000; + +const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000010000000; +const TransitionLanes: Lanes = /* */ 0b0000000001111111111111100000000; +const TransitionLane1: Lane = /* */ 0b0000000000000000000000100000000; +const TransitionLane2: Lane = /* */ 0b0000000000000000000001000000000; +const TransitionLane3: Lane = /* */ 0b0000000000000000000010000000000; +const TransitionLane4: Lane = /* */ 0b0000000000000000000100000000000; +const TransitionLane5: Lane = /* */ 0b0000000000000000001000000000000; +const TransitionLane6: Lane = /* */ 0b0000000000000000010000000000000; +const TransitionLane7: Lane = /* */ 0b0000000000000000100000000000000; +const TransitionLane8: Lane = /* */ 0b0000000000000001000000000000000; +const TransitionLane9: Lane = /* */ 0b0000000000000010000000000000000; +const TransitionLane10: Lane = /* */ 0b0000000000000100000000000000000; +const TransitionLane11: Lane = /* */ 0b0000000000001000000000000000000; +const TransitionLane12: Lane = /* */ 0b0000000000010000000000000000000; +const TransitionLane13: Lane = /* */ 0b0000000000100000000000000000000; +const TransitionLane14: Lane = /* */ 0b0000000001000000000000000000000; const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000; const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000; @@ -175,6 +176,8 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { return DefaultHydrationLane; case DefaultLane: return DefaultLane; + case GestureLane: + return GestureLane; case TransitionHydrationLane: return TransitionHydrationLane; case TransitionLane1: @@ -191,7 +194,6 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { case TransitionLane12: case TransitionLane13: case TransitionLane14: - case TransitionLane15: return lanes & TransitionLanes; case RetryLane1: case RetryLane2: @@ -459,6 +461,7 @@ function computeExpirationTime(lane: Lane, currentTime: number) { case SyncLane: case InputContinuousHydrationLane: case InputContinuousLane: + case GestureLane: // User interactions should expire slightly more quickly. // // NOTE: This is set to the corresponding constant as in Scheduler.js. @@ -486,7 +489,6 @@ function computeExpirationTime(lane: Lane, currentTime: number) { case TransitionLane12: case TransitionLane13: case TransitionLane14: - case TransitionLane15: return currentTime + transitionLaneExpirationMs; case RetryLane1: case RetryLane2: @@ -640,7 +642,8 @@ export function includesBlockingLane(lanes: Lanes): boolean { InputContinuousHydrationLane | InputContinuousLane | DefaultHydrationLane | - DefaultLane; + DefaultLane | + GestureLane; return (lanes & SyncDefaultLanes) !== NoLanes; } @@ -663,6 +666,11 @@ export function isTransitionLane(lane: Lane): boolean { return (lane & TransitionLanes) !== NoLanes; } +export function isGestureRender(lanes: Lanes): boolean { + // This should render only the one lane. + return lanes === GestureLane; +} + export function claimNextTransitionLane(): Lane { // Cycle through the lanes, assigning each new transition to the next lane. // In most cases, this means every transition gets its own lane, until we @@ -1053,7 +1061,6 @@ export function getBumpedLaneForHydrationByLane(lane: Lane): Lane { case TransitionLane12: case TransitionLane13: case TransitionLane14: - case TransitionLane15: case RetryLane1: case RetryLane2: case RetryLane3: @@ -1197,7 +1204,8 @@ export function getGroupNameOfHighestPriorityLane(lanes: Lanes): string { InputContinuousHydrationLane | InputContinuousLane | DefaultHydrationLane | - DefaultLane) + DefaultLane | + GestureLane) ) { return 'Blocking'; } diff --git a/packages/react-reconciler/src/ReactFiberRoot.js b/packages/react-reconciler/src/ReactFiberRoot.js index 4971bb4c2be34..03ddde7a5ab5b 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.js +++ b/packages/react-reconciler/src/ReactFiberRoot.js @@ -33,6 +33,7 @@ import { enableUpdaterTracking, enableTransitionTracing, disableLegacyMode, + enableSwipeTransition, } from 'shared/ReactFeatureFlags'; import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue'; import {LegacyRoot, ConcurrentRoot} from './ReactRootTags'; @@ -97,6 +98,10 @@ function FiberRootNode( this.formState = formState; + if (enableSwipeTransition) { + this.gestures = null; + } + this.incompleteTransitions = new Map(); if (enableTransitionTracing) { this.transitionCallbacks = null; diff --git a/packages/react-reconciler/src/ReactFiberRootScheduler.js b/packages/react-reconciler/src/ReactFiberRootScheduler.js index 41daa6b806065..293992e40618d 100644 --- a/packages/react-reconciler/src/ReactFiberRootScheduler.js +++ b/packages/react-reconciler/src/ReactFiberRootScheduler.js @@ -20,6 +20,7 @@ import { enableComponentPerformanceTrack, enableSiblingPrerendering, enableYieldingBeforePassive, + enableSwipeTransition, } from 'shared/ReactFeatureFlags'; import { NoLane, @@ -32,6 +33,7 @@ import { claimNextTransitionLane, getNextLanesToFlushSync, checkIfRootIsPrerendering, + isGestureRender, } from './ReactFiberLane'; import { CommitContext, @@ -211,7 +213,8 @@ function flushSyncWorkAcrossRoots_impl( rootHasPendingCommit, ); if ( - includesSyncLane(nextLanes) && + (includesSyncLane(nextLanes) || + (enableSwipeTransition && isGestureRender(nextLanes))) && !checkIfRootIsPrerendering(root, nextLanes) ) { // This root has pending sync work. Flush it now. @@ -296,7 +299,8 @@ function processRootScheduleInMicrotask() { syncTransitionLanes !== NoLanes || // Common case: we're not treating any extra lanes as synchronous, so we // can just check if the next lanes are sync. - includesSyncLane(nextLanes) + includesSyncLane(nextLanes) || + (enableSwipeTransition && isGestureRender(nextLanes)) ) { mightHavePendingSyncWork = true; } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 5f17ca31b3693..b3d1e5371c06c 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -47,6 +47,7 @@ import { enableYieldingBeforePassive, enableThrottledScheduling, enableViewTransition, + enableSwipeTransition, } from 'shared/ReactFeatureFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import is from 'shared/objectIs'; @@ -184,6 +185,8 @@ import { claimNextTransitionLane, checkIfRootIsPrerendering, includesOnlyViewTransitionEligibleLanes, + isGestureRender, + GestureLane, } from './ReactFiberLane'; import { DiscreteEventPriority, @@ -338,6 +341,7 @@ import { import {getMaskedContext, getUnmaskedContext} from './ReactFiberContext'; import {peekEntangledActionLane} from './ReactFiberAsyncAction'; import {logUncaughtError} from './ReactFiberErrorLogger'; +import {deleteScheduledGesture} from './ReactFiberGestureScheduler'; const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; @@ -3287,6 +3291,13 @@ function commitRoot( const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes(); remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes); + if (enableSwipeTransition && root.gestures === null) { + // Gestures don't clear their lanes while the gesture is still active but it + // might not be scheduled to do any more renders and so we shouldn't schedule + // any more gesture lane work until a new gesture is scheduled. + remainingLanes &= ~GestureLane; + } + markRootFinished( root, lanes, @@ -3310,6 +3321,21 @@ function commitRoot( // times out. } + if (enableSwipeTransition && isGestureRender(lanes)) { + // This is a special kind of render that doesn't commit regular effects. + commitGestureOnRoot( + root, + finishedWork, + recoverableErrors, + enableProfilerTimer + ? suspendedCommitReason === IMMEDIATE_COMMIT + ? completedRenderEndTime + : commitStartTime + : 0, + ); + return; + } + // workInProgressX might be overwritten, so we want // to store it in pendingPassiveX until they get processed // We need to pass this through as an argument to commitRoot @@ -3802,6 +3828,24 @@ function flushSpawnedWork(): void { } } +function commitGestureOnRoot( + root: FiberRoot, + finishedWork: null | Fiber, + recoverableErrors: null | Array>, + renderEndTime: number, // Profiling-only +): void { + // We assume that the gesture we just rendered was the first one in the queue. + const finishedGesture = root.gestures; + if (finishedGesture === null) { + throw new Error( + 'Finished rendering the gesture lane but there were no pending gestures. ' + + 'React should not have started a render in this case. This is a bug in React.', + ); + } + deleteScheduledGesture(root, finishedGesture); + // TODO: Run the gesture +} + function makeErrorInfo(componentStack: ?string) { const errorInfo = { componentStack, diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 98e7d4deefea3..3479eba1b9dd3 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -17,6 +17,7 @@ import type { Awaited, ReactComponentInfo, ReactDebugInfo, + StartGesture, } from 'shared/ReactTypes'; import type {WorkTag} from './ReactWorkTags'; import type {TypeOfMode} from './ReactTypeOfMode'; @@ -38,6 +39,7 @@ import type { import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates'; import type {ComponentStackNode} from 'react-server/src/ReactFizzComponentStack'; import type {ThenableState} from './ReactFiberThenable'; +import type {ScheduledGesture} from './ReactFiberGestureScheduler'; // Unwind Circular: moved from ReactFiberHooks.old export type HookType = @@ -60,7 +62,8 @@ export type HookType = | 'useCacheRefresh' | 'useOptimistic' | 'useFormState' - | 'useActionState'; + | 'useActionState' + | 'useSwipeTransition'; export type ContextDependency = { context: ReactContext, @@ -279,6 +282,9 @@ type BaseFiberRootProperties = { ) => void, formState: ReactFormState | null, + + // enableSwipeTransition only + gestures: null | ScheduledGesture, }; // The following attributes are only used by DevTools and are only present in DEV builds. @@ -442,6 +448,12 @@ export type Dispatcher = { initialState: Awaited, permalink?: string, ) => [Awaited, (P) => void, boolean], + // TODO: Non-nullable once `enableSwipeTransition` is on everywhere. + useSwipeTransition?: ( + previous: T, + current: T, + next: T, + ) => [T, StartGesture], }; export type AsyncDispatcher = { diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js index a0ec1c7414982..8ae00568ae670 100644 --- a/packages/react-server/src/ReactFizzHooks.js +++ b/packages/react-server/src/ReactFizzHooks.js @@ -16,6 +16,7 @@ import type { Usable, ReactCustomFormAction, Awaited, + StartGesture, } from 'shared/ReactTypes'; import type {ResumableState} from './ReactFizzConfig'; @@ -38,7 +39,10 @@ import { } from './ReactFizzConfig'; import {createFastHash} from './ReactServerStreamConfig'; -import {enableUseEffectEventHook} from 'shared/ReactFeatureFlags'; +import { + enableUseEffectEventHook, + enableSwipeTransition, +} from 'shared/ReactFeatureFlags'; import is from 'shared/objectIs'; import { REACT_CONTEXT_TYPE, @@ -795,6 +799,19 @@ function useMemoCache(size: number): Array { return data; } +function unsupportedStartGesture() { + throw new Error('startGesture cannot be called during server rendering.'); +} + +function useSwipeTransition( + previous: T, + current: T, + next: T, +): [T, StartGesture] { + resolveCurrentlyRenderingComponent(); + return [current, unsupportedStartGesture]; +} + function noop(): void {} function clientHookNotSupported() { @@ -837,25 +854,25 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs : { readContext, use, + useCallback, useContext, + useEffect: clientHookNotSupported, + useImperativeHandle: clientHookNotSupported, + useInsertionEffect: clientHookNotSupported, + useLayoutEffect: clientHookNotSupported, useMemo, useReducer: clientHookNotSupported, useRef: clientHookNotSupported, useState: clientHookNotSupported, - useInsertionEffect: clientHookNotSupported, - useLayoutEffect: clientHookNotSupported, - useCallback, - useImperativeHandle: clientHookNotSupported, - useEffect: clientHookNotSupported, useDebugValue: noop, useDeferredValue: clientHookNotSupported, useTransition: clientHookNotSupported, - useId, useSyncExternalStore: clientHookNotSupported, - useOptimistic, - useActionState, - useFormState: useActionState, + useId, useHostTransitionStatus, + useFormState: useActionState, + useActionState, + useOptimistic, useMemoCache, useCacheRefresh, }; @@ -863,6 +880,11 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs if (enableUseEffectEventHook) { HooksDispatcher.useEffectEvent = useEffectEvent; } +if (enableSwipeTransition) { + HooksDispatcher.useSwipeTransition = supportsClientAPIs + ? useSwipeTransition + : clientHookNotSupported; +} export let currentResumableState: null | ResumableState = (null: any); export function setCurrentResumableState( diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js index d0351e38c86aa..f1d31f3e486eb 100644 --- a/packages/react-server/src/ReactFlightHooks.js +++ b/packages/react-server/src/ReactFlightHooks.js @@ -17,6 +17,10 @@ import { } from 'shared/ReactSymbols'; import {createThenableState, trackUsedThenable} from './ReactFlightThenable'; import {isClientReference} from './ReactFlightServerConfig'; +import { + enableUseEffectEventHook, + enableSwipeTransition, +} from 'shared/ReactFeatureFlags'; let currentRequest = null; let thenableIndexCounter = 0; @@ -58,33 +62,32 @@ export function getThenableStateAfterSuspending(): ThenableState { } export const HooksDispatcher: Dispatcher = { - useMemo(nextCreate: () => T): T { - return nextCreate(); - }, + readContext: (unsupportedContext: any), + + use, useCallback(callback: T): T { return callback; }, - useDebugValue(): void {}, - useDeferredValue: (unsupportedHook: any), - useTransition: (unsupportedHook: any), - readContext: (unsupportedContext: any), useContext: (unsupportedContext: any), + useEffect: (unsupportedHook: any), + useImperativeHandle: (unsupportedHook: any), + useLayoutEffect: (unsupportedHook: any), + useInsertionEffect: (unsupportedHook: any), + useMemo(nextCreate: () => T): T { + return nextCreate(); + }, useReducer: (unsupportedHook: any), useRef: (unsupportedHook: any), useState: (unsupportedHook: any), - useInsertionEffect: (unsupportedHook: any), - useLayoutEffect: (unsupportedHook: any), - useImperativeHandle: (unsupportedHook: any), - useEffect: (unsupportedHook: any), + useDebugValue(): void {}, + useDeferredValue: (unsupportedHook: any), + useTransition: (unsupportedHook: any), + useSyncExternalStore: (unsupportedHook: any), useId, useHostTransitionStatus: (unsupportedHook: any), - useOptimistic: (unsupportedHook: any), useFormState: (unsupportedHook: any), useActionState: (unsupportedHook: any), - useSyncExternalStore: (unsupportedHook: any), - useCacheRefresh(): (?() => T, ?T) => void { - return unsupportedRefresh; - }, + useOptimistic: (unsupportedHook: any), useMemoCache(size: number): Array { const data = new Array(size); for (let i = 0; i < size; i++) { @@ -92,8 +95,16 @@ export const HooksDispatcher: Dispatcher = { } return data; }, - use, + useCacheRefresh(): (?() => T, ?T) => void { + return unsupportedRefresh; + }, }; +if (enableUseEffectEventHook) { + HooksDispatcher.useEffectEvent = (unsupportedHook: any); +} +if (enableSwipeTransition) { + HooksDispatcher.useSwipeTransition = (unsupportedHook: any); +} function unsupportedHook(): void { throw new Error('This Hook is not supported in Server Components.'); diff --git a/packages/react/index.experimental.development.js b/packages/react/index.experimental.development.js index 6074b683b781d..e8ebc236133b0 100644 --- a/packages/react/index.experimental.development.js +++ b/packages/react/index.experimental.development.js @@ -33,6 +33,7 @@ export { unstable_getCacheForType, unstable_SuspenseList, unstable_ViewTransition, + unstable_useSwipeTransition, unstable_addTransitionType, unstable_useCacheRefresh, useId, diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js index 77cf6bd0e0317..a3c6f4a10f090 100644 --- a/packages/react/index.experimental.js +++ b/packages/react/index.experimental.js @@ -33,6 +33,7 @@ export { unstable_getCacheForType, unstable_SuspenseList, unstable_ViewTransition, + unstable_useSwipeTransition, unstable_addTransitionType, unstable_useCacheRefresh, useId, diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index 715ea8ab47c35..33175a9a77f35 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -57,6 +57,7 @@ import { use, useOptimistic, useActionState, + useSwipeTransition, } from './ReactHooks'; import ReactSharedInternals from './ReactSharedInternalsClient'; import {startTransition} from './ReactStartTransition'; @@ -126,7 +127,10 @@ export { // enableViewTransition REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition, addTransitionType as unstable_addTransitionType, + // enableSwipeTransition + useSwipeTransition as unstable_useSwipeTransition, + // DEV-only useId, - act, // DEV-only - captureOwnerStack, // DEV-only + act, + captureOwnerStack, }; diff --git a/packages/react/src/ReactHooks.js b/packages/react/src/ReactHooks.js index ba6b0ffbcb71e..605337480f731 100644 --- a/packages/react/src/ReactHooks.js +++ b/packages/react/src/ReactHooks.js @@ -13,12 +13,16 @@ import type { StartTransitionOptions, Usable, Awaited, + StartGesture, } from 'shared/ReactTypes'; import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols'; import ReactSharedInternals from 'shared/ReactSharedInternals'; -import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags'; +import { + enableUseEffectCRUDOverload, + enableSwipeTransition, +} from 'shared/ReactFeatureFlags'; type BasicStateAction = (S => S) | S; type Dispatch = A => void; @@ -261,3 +265,16 @@ export function useActionState( const dispatcher = resolveDispatcher(); return dispatcher.useActionState(action, initialState, permalink); } + +export function useSwipeTransition( + previous: T, + current: T, + next: T, +): [T, StartGesture] { + if (!enableSwipeTransition) { + throw new Error('Not implemented.'); + } + const dispatcher = resolveDispatcher(); + // $FlowFixMe[not-a-function] This is unstable, thus optional + return dispatcher.useSwipeTransition(previous, current, next); +} diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 844b2a2f6a914..ea09d0a3c76e3 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -92,6 +92,8 @@ export const enableHalt = __EXPERIMENTAL__; export const enableViewTransition = __EXPERIMENTAL__; +export const enableSwipeTransition = __EXPERIMENTAL__; + /** * Switches Fiber creation to a simple object instead of a constructor. */ diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 735f96a36f509..0402f9aac1832 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -168,6 +168,12 @@ export type ReactFormState = [ number /* number of bound arguments */, ]; +// Intrinsic GestureProvider. This type varies by Environment whether a particular +// renderer supports it. +export type GestureProvider = AnimationTimeline; // TODO: More provider types. + +export type StartGesture = (gestureProvider: GestureProvider) => () => void; + export type Awaited = T extends null | void ? T // special case for `null | undefined` when not in `--strictNullChecks` mode : T extends Object // `await` only unwraps object types with a callable then. Non-object types are not unwrapped. diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 2eff113ae42f1..470e148959327 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -82,6 +82,7 @@ export const enableHydrationLaneScheduling = true; export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; export const enableViewTransition = false; +export const enableSwipeTransition = false; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index bcd8b375eca67..361ebde4136ce 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -71,6 +71,7 @@ export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; export const enableViewTransition = false; +export const enableSwipeTransition = false; export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index ef3c300602bdb..5945416050286 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -70,6 +70,7 @@ export const enableYieldingBeforePassive = true; export const enableThrottledScheduling = false; export const enableViewTransition = false; +export const enableSwipeTransition = false; export const enableFastAddPropertiesInDiffing = true; export const enableLazyPublicInstanceInFabric = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 743ba39a86792..18172fdfe5724 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -67,6 +67,7 @@ export const enableHydrationLaneScheduling = true; export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; export const enableViewTransition = false; +export const enableSwipeTransition = false; export const enableRemoveConsolePatches = false; export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 26747d3fb69a9..628a834133f2f 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -82,6 +82,7 @@ export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; export const enableViewTransition = false; +export const enableSwipeTransition = false; export const enableRemoveConsolePatches = false; export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 8da74c54bd69e..5932c6eddf6f1 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -112,5 +112,7 @@ export const enableShallowPropDiffing = false; export const enableLazyPublicInstanceInFabric = false; +export const enableSwipeTransition = false; + // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 6ab654f1d35e5..f001b660bd2aa 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -531,5 +531,7 @@ "543": "Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React.", "544": "Found a pair with an auto name. This is a bug in React.", "545": "The %s tag may only be rendered once.", - "546": "useEffect CRUD overload is not enabled in this build of React." + "546": "useEffect CRUD overload is not enabled in this build of React.", + "547": "startGesture cannot be called during server rendering.", + "548": "Finished rendering the gesture lane but there were no pending gestures. React should not have started a render in this case. This is a bug in React." } From e670e72fa076449e40172e20d17cc67c1c15419c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Ma=C5=82ecki?= Date: Fri, 14 Feb 2025 15:10:37 +0100 Subject: [PATCH 29/73] Change TouchedViewDataAtPoint type in ReactNativeTypes to use supported by Flow tooling syntax (#32382) ## Summary The `flow-api-translator` from the `hermes` repo does not support flow type spreads. It is currently not able to digest the ReactNativeTypes file as it contains unsupported syntax. The simplest solution is to change the type of the `TouchedViewDataAtPoint` to equivalent, yet supported by the Flow tooling. In this case the intersection can be used as the `TouchedViewDataAtPoint` and `InspectorData` have no common property. ## How did you test this change? Run yarn flow native --- .../src/ReactNativeTypes.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/react-native-renderer/src/ReactNativeTypes.js b/packages/react-native-renderer/src/ReactNativeTypes.js index 8bce919d385e9..261e1f0b63104 100644 --- a/packages/react-native-renderer/src/ReactNativeTypes.js +++ b/packages/react-native-renderer/src/ReactNativeTypes.js @@ -164,18 +164,19 @@ export type InspectorData = $ReadOnly<{ componentStack: string, }>; -export type TouchedViewDataAtPoint = $ReadOnly<{ - pointerY: number, - touchedViewTag?: number, - frame: $ReadOnly<{ - top: number, - left: number, - width: number, - height: number, - }>, - closestPublicInstance?: PublicInstance, - ...InspectorData, -}>; +export type TouchedViewDataAtPoint = $ReadOnly< + { + pointerY: number, + touchedViewTag?: number, + frame: $ReadOnly<{ + top: number, + left: number, + width: number, + height: number, + }>, + closestPublicInstance?: PublicInstance, + } & InspectorData, +>; export type RenderRootOptions = { onUncaughtError?: ( From 0d9834caeb3b334eaa1be45f136499f51303e7d3 Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 16 Feb 2025 09:38:13 -0600 Subject: [PATCH 30/73] build: add support to the rollup build for building typescript packages (#32393) --- babel.config-ts.js | 15 + package.json | 5 +- .../__tests__/ReactTypeScriptClass-test.ts | 4 +- scripts/jest/config.base.js | 6 +- scripts/rollup/build.js | 45 +- scripts/rollup/bundles.js | 7 + scripts/rollup/packaging.js | 4 + scripts/rollup/wrappers.js | 36 ++ yarn.lock | 488 +++++++++++++----- 9 files changed, 459 insertions(+), 151 deletions(-) create mode 100644 babel.config-ts.js diff --git a/babel.config-ts.js b/babel.config-ts.js new file mode 100644 index 0000000000000..20b35ec013e0f --- /dev/null +++ b/babel.config-ts.js @@ -0,0 +1,15 @@ +/** + * This file is purely being used for local jest runs, and doesn't participate in the build process. + */ +'use strict'; + +module.exports = { + plugins: [ + '@babel/plugin-syntax-jsx', + '@babel/plugin-transform-flow-strip-types', + ], + presets: [ + ['@babel/preset-env', {targets: {node: 'current'}}], + '@babel/preset-typescript', + ], +}; diff --git a/package.json b/package.json index cffd0ba28b493..95fffa700a7e7 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,13 @@ "@babel/plugin-transform-template-literals": "^7.10.5", "@babel/preset-flow": "^7.10.4", "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.26.0", "@babel/traverse": "^7.11.0", "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", + "@rollup/plugin-typescript": "^12.1.2", "abortcontroller-polyfill": "^1.7.5", "art": "0.10.1", "babel-plugin-syntax-trailing-function-commas": "^6.5.0", @@ -90,6 +92,7 @@ "react-lifecycles-compat": "^3.0.4", "rimraf": "^3.0.0", "rollup": "^3.29.5", + "rollup-plugin-dts": "^6.1.1", "rollup-plugin-prettier": "^4.1.1", "rollup-plugin-strip-banner": "^3.0.0", "semver": "^7.1.1", @@ -98,7 +101,7 @@ "targz": "^1.0.1", "through2": "^3.0.1", "tmp": "^0.1.0", - "typescript": "^3.7.5", + "typescript": "^5.4.3", "undici": "^5.28.4", "web-streams-polyfill": "^3.1.1", "yargs": "^15.3.1" diff --git a/packages/react/src/__tests__/ReactTypeScriptClass-test.ts b/packages/react/src/__tests__/ReactTypeScriptClass-test.ts index 5f51cc6f38c77..603edb86ea495 100644 --- a/packages/react/src/__tests__/ReactTypeScriptClass-test.ts +++ b/packages/react/src/__tests__/ReactTypeScriptClass-test.ts @@ -529,7 +529,7 @@ describe('ReactTypeScriptClass', function () { 'StateBasedOnContext uses the legacy contextTypes API which will soon be removed. ' + 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' + (ReactFeatureFlags.enableOwnerStacks - ? ' in ProvideChildContextTypes.Object..ProvideChildContextTypes (at **)' + ? ' in ProvideChildContextTypes.createElement (at **)' : ' in StateBasedOnContext (at **)\n') + ' in ProvideChildContextTypes (at **)', ]); @@ -725,7 +725,7 @@ describe('ReactTypeScriptClass', function () { 'ReadContext uses the legacy contextTypes API which will soon be removed. ' + 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' + (ReactFeatureFlags.enableOwnerStacks - ? ' in ProvideContext.Object..ProvideContext (at **)' + ? ' in ProvideContext.createElement (at **)' : ' in ReadContext (at **)\n') + ' in ProvideContext (at **)', ]); diff --git a/scripts/jest/config.base.js b/scripts/jest/config.base.js index 6fa4a3619429f..262c6ab18a144 100644 --- a/scripts/jest/config.base.js +++ b/scripts/jest/config.base.js @@ -7,7 +7,11 @@ module.exports = { '/scripts/bench/', ], transform: { - '.*': require.resolve('./preprocessor.js'), + '^.+\\.ts$': [ + 'babel-jest', + {configFile: require.resolve('../../babel.config-ts.js')}, + ], + '.(?!ts$)': require.resolve('./preprocessor.js'), }, prettierPath: require.resolve('prettier-2'), setupFiles: [require.resolve('./setupEnvironment.js')], diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 547362dae7123..53b308d16a2d8 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -4,8 +4,10 @@ const rollup = require('rollup'); const babel = require('@rollup/plugin-babel').babel; const closure = require('./plugins/closure-plugin'); const flowRemoveTypes = require('flow-remove-types'); +const {dts} = require('rollup-plugin-dts'); const prettier = require('rollup-plugin-prettier'); const replace = require('@rollup/plugin-replace'); +const typescript = require('@rollup/plugin-typescript'); const stripBanner = require('rollup-plugin-strip-banner'); const chalk = require('chalk'); const resolve = require('@rollup/plugin-node-resolve').nodeResolve; @@ -61,6 +63,8 @@ const { RN_FB_PROD, RN_FB_PROFILING, BROWSER_SCRIPT, + CJS_DTS, + ESM_DTS, } = Bundles.bundleTypes; const {getFilename} = Bundles; @@ -250,9 +254,11 @@ function getFormat(bundleType) { case RN_FB_DEV: case RN_FB_PROD: case RN_FB_PROFILING: + case CJS_DTS: return `cjs`; case ESM_DEV: case ESM_PROD: + case ESM_DTS: return `es`; case BROWSER_SCRIPT: return `iife`; @@ -281,6 +287,8 @@ function isProductionBundleType(bundleType) { case RN_FB_PROD: case RN_FB_PROFILING: case BROWSER_SCRIPT: + case CJS_DTS: + case ESM_DTS: return true; default: throw new Error(`Unknown type: ${bundleType}`); @@ -303,6 +311,8 @@ function isProfilingBundleType(bundleType) { case ESM_DEV: case ESM_PROD: case BROWSER_SCRIPT: + case CJS_DTS: + case ESM_DTS: return false; case FB_WWW_PROFILING: case NODE_PROFILING: @@ -368,27 +378,36 @@ function getPlugins( pureExternalModules, bundle ) { + // Short-circuit if we're only building a .d.ts bundle + if (bundleType === CJS_DTS || bundleType === ESM_DTS) { + return [dts({tsconfig: bundle.tsconfig})]; + } try { const forks = Modules.getForks(bundleType, entry, moduleType, bundle); const isProduction = isProductionBundleType(bundleType); const isProfiling = isProfilingBundleType(bundleType); const needsMinifiedByClosure = - bundleType !== ESM_PROD && bundleType !== ESM_DEV; + bundleType !== ESM_PROD && + bundleType !== ESM_DEV && + // TODO(@poteto) figure out ICE in closure compiler for eslint-plugin-react-hooks (ts) + bundle.tsconfig == null; return [ // Keep dynamic imports as externals dynamicImports(), - { - name: 'rollup-plugin-flow-remove-types', - transform(code) { - const transformed = flowRemoveTypes(code); - return { - code: transformed.toString(), - map: null, - }; - }, - }, + bundle.tsconfig != null + ? typescript({tsconfig: bundle.tsconfig}) + : { + name: 'rollup-plugin-flow-remove-types', + transform(code) { + const transformed = flowRemoveTypes(code); + return { + code: transformed.toString(), + map: null, + }; + }, + }, // Shim any modules that need forking in this environment. useForks(forks), // Ensure we don't try to bundle any fbjs modules. @@ -839,7 +858,9 @@ async function buildEverything() { [bundle, RN_FB_DEV], [bundle, RN_FB_PROD], [bundle, RN_FB_PROFILING], - [bundle, BROWSER_SCRIPT] + [bundle, BROWSER_SCRIPT], + [bundle, CJS_DTS], + [bundle, ESM_DTS] ); } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 04bfb1a0e5479..c66cff82c5ac2 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -26,6 +26,8 @@ const bundleTypes = { RN_FB_PROD: 'RN_FB_PROD', RN_FB_PROFILING: 'RN_FB_PROFILING', BROWSER_SCRIPT: 'BROWSER_SCRIPT', + CJS_DTS: 'CJS_DTS', + ESM_DTS: 'ESM_DTS', }; const { @@ -47,6 +49,8 @@ const { RN_FB_PROD, RN_FB_PROFILING, BROWSER_SCRIPT, + CJS_DTS, + ESM_DTS, } = bundleTypes; const moduleTypes = { @@ -1270,6 +1274,9 @@ function getFilename(bundle, bundleType) { return `${globalName}-profiling.js`; case BROWSER_SCRIPT: return `${name}.js`; + case CJS_DTS: + case ESM_DTS: + return `${name}.d.ts`; } } diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js index bc83cb8789eb8..564c40f71b922 100644 --- a/scripts/rollup/packaging.js +++ b/scripts/rollup/packaging.js @@ -34,6 +34,8 @@ const { RN_FB_PROD, RN_FB_PROFILING, BROWSER_SCRIPT, + CJS_DTS, + ESM_DTS, } = Bundles.bundleTypes; function getPackageName(name) { @@ -49,6 +51,7 @@ function getBundleOutputPath(bundle, bundleType, filename, packageName) { return `build/node_modules/${packageName}/cjs/${filename}`; case ESM_DEV: case ESM_PROD: + case ESM_DTS: return `build/node_modules/${packageName}/esm/${filename}`; case BUN_DEV: case BUN_PROD: @@ -56,6 +59,7 @@ function getBundleOutputPath(bundle, bundleType, filename, packageName) { case NODE_DEV: case NODE_PROD: case NODE_PROFILING: + case CJS_DTS: return `build/node_modules/${packageName}/cjs/${filename}`; case FB_WWW_DEV: case FB_WWW_PROD: diff --git a/scripts/rollup/wrappers.js b/scripts/rollup/wrappers.js index e01ca30a6d054..1773253cc9653 100644 --- a/scripts/rollup/wrappers.js +++ b/scripts/rollup/wrappers.js @@ -21,6 +21,8 @@ const { RN_FB_PROD, RN_FB_PROFILING, BROWSER_SCRIPT, + CJS_DTS, + ESM_DTS, } = bundleTypes; const {RECONCILER} = moduleTypes; @@ -248,6 +250,16 @@ ${source} Object.defineProperty(module.exports, "__esModule", { value: true }); `; }, + + /***************** CJS_DTS *****************/ + [CJS_DTS](source, globalName, filename, moduleType) { + return source; + }, + + /***************** ESM_DTS *****************/ + [ESM_DTS](source, globalName, filename, moduleType) { + return source; + }, }; const licenseHeaderWrappers = { @@ -464,6 +476,30 @@ ${license} * @preventMunge */ +${source}`; + }, + + /***************** CJS_DTS *****************/ + [CJS_DTS](source, globalName, filename, moduleType) { + return `/** + * @license React + * ${filename} + * +${license} + */ + +${source}`; + }, + + /***************** ESM_DTS *****************/ + [ESM_DTS](source, globalName, filename, moduleType) { + return `/** + * @license React + * ${filename} + * +${license} + */ + ${source}`; }, }; diff --git a/yarn.lock b/yarn.lock index 4922028dafc3b..44d590625111c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,6 +68,15 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" +"@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -225,6 +234,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.9.tgz#75a9482ad3d0cc7188a537aa4910bc59db67cbca" + integrity sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg== + dependencies: + "@babel/parser" "^7.26.9" + "@babel/types" "^7.26.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/generator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.3.tgz#0e22c005b0a94c1c74eafe19ef78ce53a4d45c03" @@ -263,6 +283,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== + dependencies: + "@babel/types" "^7.25.9" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" @@ -369,6 +396,19 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" +"@babel/helper-create-class-features-plugin@^7.25.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" + integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/helper-replace-supers" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/traverse" "^7.26.9" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" @@ -506,6 +546,14 @@ dependencies: "@babel/types" "^7.20.7" +"@babel/helper-member-expression-to-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" + integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-module-imports@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" @@ -534,6 +582,14 @@ dependencies: "@babel/types" "^7.22.15" +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" @@ -572,6 +628,15 @@ "@babel/helper-split-export-declaration" "^7.24.5" "@babel/helper-validator-identifier" "^7.24.5" +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-optimise-call-expression@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" @@ -593,6 +658,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-optimise-call-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" + integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== + dependencies: + "@babel/types" "^7.25.9" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" @@ -608,6 +680,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + "@babel/helper-plugin-utils@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" @@ -663,6 +740,15 @@ "@babel/traverse" "^7.20.7" "@babel/types" "^7.20.7" +"@babel/helper-replace-supers@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" + integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/traverse" "^7.26.5" + "@babel/helper-simple-access@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" @@ -706,6 +792,14 @@ dependencies: "@babel/types" "^7.20.0" +"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" + integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" @@ -744,6 +838,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + "@babel/helper-validator-identifier@^7.10.4": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" @@ -769,6 +868,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" @@ -779,6 +883,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + "@babel/helper-wrap-function@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" @@ -905,6 +1014,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== +"@babel/parser@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5" + integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A== + dependencies: + "@babel/types" "^7.26.9" + "@babel/plugin-external-helpers@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.10.4.tgz#40d38e8e48a1fa3766ab43496253266ca26783ce" @@ -1197,6 +1313,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx@^7.7.2": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" @@ -1281,6 +1404,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-typescript@^7.7.2": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" @@ -1533,6 +1663,14 @@ "@babel/helper-simple-access" "^7.16.0" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-commonjs@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" + integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== + dependencies: + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-modules-systemjs@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" @@ -1794,6 +1932,17 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript" "^7.16.0" +"@babel/plugin-transform-typescript@^7.25.9": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz#2e9caa870aa102f50d7125240d9dbf91334b0950" + integrity sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-syntax-typescript" "^7.25.9" + "@babel/plugin-transform-unicode-escapes@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" @@ -1936,6 +2085,17 @@ "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-transform-typescript" "^7.16.0" +"@babel/preset-typescript@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" + integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.25.9" + "@babel/plugin-transform-typescript" "^7.25.9" + "@babel/register@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.14.5.tgz#d0eac615065d9c2f1995842f85d6e56c345f3233" @@ -1995,6 +2155,15 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/template@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" + integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/parser" "^7.26.9" + "@babel/types" "^7.26.9" + "@babel/template@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" @@ -2096,6 +2265,19 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a" + integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.9" + "@babel/parser" "^7.26.9" + "@babel/template" "^7.26.9" + "@babel/types" "^7.26.9" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.7", "@babel/types@^7.23.0", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.3.3": version "7.24.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" @@ -2148,6 +2330,14 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.25.9", "@babel/types@^7.26.9": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce" + integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/types@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" @@ -2785,6 +2975,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" @@ -3175,6 +3370,14 @@ "@rollup/pluginutils" "^5.0.1" magic-string "^0.27.0" +"@rollup/plugin-typescript@^12.1.2": + version "12.1.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz#ebaeec2e7376faa889030ccd7cb485a649e63118" + integrity sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg== + dependencies: + "@rollup/pluginutils" "^5.1.0" + resolve "^1.22.1" + "@rollup/pluginutils@^5.0.1": version "5.0.2" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33" @@ -3184,100 +3387,109 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rollup/rollup-android-arm-eabi@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz#c18bad635ba24220a6c8cc427ab2cab12e1531a3" - integrity sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA== - -"@rollup/rollup-android-arm64@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.1.tgz#b5c00344b80f20889b72bfe65d3c209cef247362" - integrity sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q== - -"@rollup/rollup-darwin-arm64@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.1.tgz#78e5358d4a2a08c090f75dd87fa2eada42eca1e5" - integrity sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA== - -"@rollup/rollup-darwin-x64@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.1.tgz#c04c9e173244d44de50278f3f893fb68d987fcc6" - integrity sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q== - -"@rollup/rollup-freebsd-arm64@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.1.tgz#3bdf18d4ef32dcfe9b20bba18d7a53a101ed79d9" - integrity sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA== - -"@rollup/rollup-freebsd-x64@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.1.tgz#35867b15c276f4b4ca8eb226f7dd6df8c64640db" - integrity sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw== - -"@rollup/rollup-linux-arm-gnueabihf@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.1.tgz#92c212d1b38c105bd1eb101254722d27d869b1ac" - integrity sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g== - -"@rollup/rollup-linux-arm-musleabihf@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.1.tgz#ebb94d8cd438f23e38caa4a87ca80d4cf5b50fa1" - integrity sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q== - -"@rollup/rollup-linux-arm64-gnu@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.1.tgz#ce6a5eacbd5fd4bdf7bf27bd818980230bdb9fab" - integrity sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw== - -"@rollup/rollup-linux-arm64-musl@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.1.tgz#31b4e0a543607e6eb4f982ffb45830919a952a83" - integrity sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw== - -"@rollup/rollup-linux-loongarch64-gnu@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.1.tgz#ad7b35f193f1d2e0dc37eba733069b4af5f6498d" - integrity sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.1.tgz#b713a55d7eac4d2c8a0109c3daca6ea85fc178b3" - integrity sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg== - -"@rollup/rollup-linux-riscv64-gnu@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.1.tgz#bea4fd8ad190e9bc1d11efafa2efc9d121f50b96" - integrity sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g== - -"@rollup/rollup-linux-s390x-gnu@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.1.tgz#cc98c32733ca472635759c78a79b5f8d887b2a6a" - integrity sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ== - -"@rollup/rollup-linux-x64-gnu@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.1.tgz#5c009c264a7ce0e19b40890ca9945440bb420691" - integrity sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg== - -"@rollup/rollup-linux-x64-musl@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.1.tgz#73d2f44070c23e031262b601927fdb4eec253bc1" - integrity sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA== - -"@rollup/rollup-win32-arm64-msvc@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.1.tgz#fa106304818078f9d3fc9005642ad99f596eed2d" - integrity sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ== - -"@rollup/rollup-win32-ia32-msvc@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.1.tgz#a1a394c705a0d2a974a124c4b471fc1cf851a56f" - integrity sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ== - -"@rollup/rollup-win32-x64-msvc@4.32.1": - version "4.32.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.1.tgz#512db088df67afee8f07183cdf8c9eecd64f6ef8" - integrity sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q== +"@rollup/pluginutils@^5.1.0": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + +"@rollup/rollup-android-arm-eabi@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz#e554185b1afa5509a7a4040d15ec0c3b4435ded1" + integrity sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw== + +"@rollup/rollup-android-arm64@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz#b1ee64bb413b2feba39803b0a1bebf2a9f3d70e1" + integrity sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg== + +"@rollup/rollup-darwin-arm64@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz#bfdce3e07a345dd1bd628f3b796050f39629d7f0" + integrity sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA== + +"@rollup/rollup-darwin-x64@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz#781a94a537c57bdf0a500e47a25ab5985e5e8dff" + integrity sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA== + +"@rollup/rollup-freebsd-arm64@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz#7a028357cbd12c5869c446ad18177c89f3405102" + integrity sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA== + +"@rollup/rollup-freebsd-x64@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz#f24836a6371cccc4408db74f0fd986dacf098950" + integrity sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q== + +"@rollup/rollup-linux-arm-gnueabihf@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz#95f27e96f0eb9b9ae9887739a8b6dffc90c1237f" + integrity sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ== + +"@rollup/rollup-linux-arm-musleabihf@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz#677b34fba9d070877736c3fe8b02aacb5e142d97" + integrity sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw== + +"@rollup/rollup-linux-arm64-gnu@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz#32d3d19dedde54e91574a098f22ea43a09cf63dd" + integrity sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA== + +"@rollup/rollup-linux-arm64-musl@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz#a58dff44a18696df65ed8c0ad68a2945cf900484" + integrity sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw== + +"@rollup/rollup-linux-loongarch64-gnu@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz#a7488ab078233111e8aeb370d1ecf107ec7e1716" + integrity sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw== + +"@rollup/rollup-linux-powerpc64le-gnu@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz#e9b9c0d6bd248a92b2d6ec01ebf99c62ae1f2e9a" + integrity sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w== + +"@rollup/rollup-linux-riscv64-gnu@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz#0df84ce2bea48ee686fb55060d76ab47aff45c4c" + integrity sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw== + +"@rollup/rollup-linux-s390x-gnu@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz#73df374c57d036856e33dbd2715138922e91e452" + integrity sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw== + +"@rollup/rollup-linux-x64-gnu@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz#f27af0b55f0cdd84e182e6cd44a6d03da0458149" + integrity sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA== + +"@rollup/rollup-linux-x64-musl@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz#c7981ad5cfb8c3cd5d643d33ca54e4d2802b9201" + integrity sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ== + +"@rollup/rollup-win32-arm64-msvc@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz#06cedc0ef3cbf1cbd8abcf587090712e40ae6941" + integrity sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw== + +"@rollup/rollup-win32-ia32-msvc@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz#90b39b977b14961a769be6ea61238e7fc668dd4d" + integrity sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg== + +"@rollup/rollup-win32-x64-msvc@4.34.7": + version "4.34.7" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz#6531d61e7141091eaab0461ee8e0380c10e4ca57" + integrity sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w== "@sinclair/typebox@^0.25.16": version "0.25.24" @@ -3468,21 +3680,11 @@ dependencies: "@types/estree" "*" -"@types/estree@*": - version "0.0.42" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.42.tgz#8d0c1f480339efedb3e46070e22dd63e0430dd11" - integrity sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ== - -"@types/estree@1.0.6", "@types/estree@^1.0.6": +"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== -"@types/estree@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": version "4.17.35" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" @@ -11370,6 +11572,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" @@ -11976,6 +12183,13 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" +magic-string@^0.30.10: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -14343,9 +14557,9 @@ readdirp@^2.2.1: readable-stream "^2.0.2" readdirp@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.1.tgz#bd115327129672dc47f87408f05df9bd9ca3ef55" - integrity sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw== + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== readdirp@~3.6.0: version "3.6.0" @@ -14744,6 +14958,15 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" +rollup-plugin-dts@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-6.1.1.tgz#46b33f4d1d7f4e66f1171ced9b282ac11a15a254" + integrity sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA== + dependencies: + magic-string "^0.30.10" + optionalDependencies: + "@babel/code-frame" "^7.24.2" + rollup-plugin-prettier@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/rollup-plugin-prettier/-/rollup-plugin-prettier-4.1.1.tgz#eb74bd47c3cc3ba68bdf34b5323d0d7a47be8cec" @@ -14781,31 +15004,31 @@ rollup@^3.29.5: fsevents "~2.3.2" rollup@^4.24.0: - version "4.32.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.32.1.tgz#95309604d92c3d21cbf06c3ee46a098209ce13a4" - integrity sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA== + version "4.34.7" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.7.tgz#e00d8550688a616a3481c6446bb688d4c753ba8f" + integrity sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ== dependencies: "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.32.1" - "@rollup/rollup-android-arm64" "4.32.1" - "@rollup/rollup-darwin-arm64" "4.32.1" - "@rollup/rollup-darwin-x64" "4.32.1" - "@rollup/rollup-freebsd-arm64" "4.32.1" - "@rollup/rollup-freebsd-x64" "4.32.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.32.1" - "@rollup/rollup-linux-arm-musleabihf" "4.32.1" - "@rollup/rollup-linux-arm64-gnu" "4.32.1" - "@rollup/rollup-linux-arm64-musl" "4.32.1" - "@rollup/rollup-linux-loongarch64-gnu" "4.32.1" - "@rollup/rollup-linux-powerpc64le-gnu" "4.32.1" - "@rollup/rollup-linux-riscv64-gnu" "4.32.1" - "@rollup/rollup-linux-s390x-gnu" "4.32.1" - "@rollup/rollup-linux-x64-gnu" "4.32.1" - "@rollup/rollup-linux-x64-musl" "4.32.1" - "@rollup/rollup-win32-arm64-msvc" "4.32.1" - "@rollup/rollup-win32-ia32-msvc" "4.32.1" - "@rollup/rollup-win32-x64-msvc" "4.32.1" + "@rollup/rollup-android-arm-eabi" "4.34.7" + "@rollup/rollup-android-arm64" "4.34.7" + "@rollup/rollup-darwin-arm64" "4.34.7" + "@rollup/rollup-darwin-x64" "4.34.7" + "@rollup/rollup-freebsd-arm64" "4.34.7" + "@rollup/rollup-freebsd-x64" "4.34.7" + "@rollup/rollup-linux-arm-gnueabihf" "4.34.7" + "@rollup/rollup-linux-arm-musleabihf" "4.34.7" + "@rollup/rollup-linux-arm64-gnu" "4.34.7" + "@rollup/rollup-linux-arm64-musl" "4.34.7" + "@rollup/rollup-linux-loongarch64-gnu" "4.34.7" + "@rollup/rollup-linux-powerpc64le-gnu" "4.34.7" + "@rollup/rollup-linux-riscv64-gnu" "4.34.7" + "@rollup/rollup-linux-s390x-gnu" "4.34.7" + "@rollup/rollup-linux-x64-gnu" "4.34.7" + "@rollup/rollup-linux-x64-musl" "4.34.7" + "@rollup/rollup-win32-arm64-msvc" "4.34.7" + "@rollup/rollup-win32-ia32-msvc" "4.34.7" + "@rollup/rollup-win32-x64-msvc" "4.34.7" fsevents "~2.3.2" rrweb-cssom@^0.6.0: @@ -16530,11 +16753,6 @@ typescript@3.9.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a" integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ== -typescript@^3.7.5: - version "3.7.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" - integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== - typescript@^5.4.3: version "5.7.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" From 037b25cfdcd18deea0e1c6c2e8d2548dbf32f7f3 Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 16 Feb 2025 12:23:44 -0600 Subject: [PATCH 31/73] test(eslint): create eslint test fixtures (#32396) --- fixtures/{eslint => eslint-v6}/.eslintrc.json | 7 +- fixtures/eslint-v6/README.md | 12 + fixtures/eslint-v6/build.mjs | 5 + fixtures/eslint-v6/index.js | 143 +++ fixtures/eslint-v6/package.json | 12 + fixtures/eslint-v6/yarn.lock | 865 ++++++++++++++++++ fixtures/eslint-v7/.eslintrc.json | 14 + fixtures/eslint-v7/README.md | 12 + fixtures/eslint-v7/build.mjs | 5 + fixtures/eslint-v7/index.js | 143 +++ fixtures/eslint-v7/package.json | 12 + fixtures/eslint-v7/yarn.lock | 769 ++++++++++++++++ fixtures/eslint-v8/.eslintrc.json | 14 + fixtures/eslint-v8/README.md | 12 + fixtures/eslint-v8/build.mjs | 5 + fixtures/eslint-v8/index.js | 143 +++ fixtures/eslint-v8/package.json | 12 + fixtures/eslint-v8/yarn.lock | 676 ++++++++++++++ fixtures/eslint-v9/README.md | 12 + fixtures/eslint-v9/build.mjs | 5 + fixtures/eslint-v9/eslint.config.mjs | 21 + fixtures/eslint-v9/index.js | 143 +++ fixtures/eslint-v9/package.json | 12 + fixtures/eslint-v9/yarn.lock | 579 ++++++++++++ fixtures/eslint/README.md | 7 - fixtures/eslint/index.js | 26 - fixtures/eslint/package.json | 12 - fixtures/eslint/proxy/index.js | 35 - fixtures/eslint/proxy/package.json | 4 - fixtures/eslint/watch.sh | 3 - fixtures/eslint/yarn.lock | 853 ----------------- 31 files changed, 3629 insertions(+), 944 deletions(-) rename fixtures/{eslint => eslint-v6}/.eslintrc.json (52%) create mode 100644 fixtures/eslint-v6/README.md create mode 100644 fixtures/eslint-v6/build.mjs create mode 100644 fixtures/eslint-v6/index.js create mode 100644 fixtures/eslint-v6/package.json create mode 100644 fixtures/eslint-v6/yarn.lock create mode 100644 fixtures/eslint-v7/.eslintrc.json create mode 100644 fixtures/eslint-v7/README.md create mode 100644 fixtures/eslint-v7/build.mjs create mode 100644 fixtures/eslint-v7/index.js create mode 100644 fixtures/eslint-v7/package.json create mode 100644 fixtures/eslint-v7/yarn.lock create mode 100644 fixtures/eslint-v8/.eslintrc.json create mode 100644 fixtures/eslint-v8/README.md create mode 100644 fixtures/eslint-v8/build.mjs create mode 100644 fixtures/eslint-v8/index.js create mode 100644 fixtures/eslint-v8/package.json create mode 100644 fixtures/eslint-v8/yarn.lock create mode 100644 fixtures/eslint-v9/README.md create mode 100644 fixtures/eslint-v9/build.mjs create mode 100644 fixtures/eslint-v9/eslint.config.mjs create mode 100644 fixtures/eslint-v9/index.js create mode 100644 fixtures/eslint-v9/package.json create mode 100644 fixtures/eslint-v9/yarn.lock delete mode 100644 fixtures/eslint/README.md delete mode 100644 fixtures/eslint/index.js delete mode 100644 fixtures/eslint/package.json delete mode 100644 fixtures/eslint/proxy/index.js delete mode 100644 fixtures/eslint/proxy/package.json delete mode 100755 fixtures/eslint/watch.sh delete mode 100644 fixtures/eslint/yarn.lock diff --git a/fixtures/eslint/.eslintrc.json b/fixtures/eslint-v6/.eslintrc.json similarity index 52% rename from fixtures/eslint/.eslintrc.json rename to fixtures/eslint-v6/.eslintrc.json index e03178a42a72a..33b2cfaacabcd 100644 --- a/fixtures/eslint/.eslintrc.json +++ b/fixtures/eslint-v6/.eslintrc.json @@ -1,15 +1,14 @@ { "root": true, + "extends": ["plugin:react-hooks/recommended-legacy"], "parserOptions": { - "ecmaVersion": 8, + "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, - "plugins": ["react-hooks"], "rules": { - "react-hooks/rules-of-hooks": 2, - "react-hooks/exhaustive-deps": 2 + "react-hooks/exhaustive-deps": "error" } } diff --git a/fixtures/eslint-v6/README.md b/fixtures/eslint-v6/README.md new file mode 100644 index 0000000000000..7f0ebe8b038ba --- /dev/null +++ b/fixtures/eslint-v6/README.md @@ -0,0 +1,12 @@ +# ESLint v6 Fixture + +This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 6. + +Run the following to test. + +```sh +cd fixtures/eslint-v6 +yarn +yarn build +yarn lint +``` diff --git a/fixtures/eslint-v6/build.mjs b/fixtures/eslint-v6/build.mjs new file mode 100644 index 0000000000000..02a1bbd25e00e --- /dev/null +++ b/fixtures/eslint-v6/build.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import {exec} from 'node:child_process'; + +exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); diff --git a/fixtures/eslint-v6/index.js b/fixtures/eslint-v6/index.js new file mode 100644 index 0000000000000..6d4a7235dd257 --- /dev/null +++ b/fixtures/eslint-v6/index.js @@ -0,0 +1,143 @@ +/** + * Exhaustive Deps + */ +// Valid because dependencies are declared correctly +function Comment({comment, commentSource}) { + const currentUserID = comment.viewer.id; + const environment = RelayEnvironment.forUser(currentUserID); + const commentID = nullthrows(comment.id); + useEffect(() => { + const subscription = SubscriptionCounter.subscribeOnce( + `StoreSubscription_${commentID}`, + () => + StoreSubscription.subscribe( + environment, + { + comment_id: commentID, + }, + currentUserID, + commentSource + ) + ); + return () => subscription.dispose(); + }, [commentID, commentSource, currentUserID, environment]); +} + +// Valid because no dependencies +function UseEffectWithNoDependencies() { + const local = {}; + useEffect(() => { + console.log(local); + }); +} +function UseEffectWithEmptyDependencies() { + useEffect(() => { + const local = {}; + console.log(local); + }, []); +} + +// OK because `props` wasn't defined. +function ComponentWithNoPropsDefined() { + useEffect(() => { + console.log(props.foo); + }, []); +} + +// Valid because props are declared as a dependency +function ComponentWithPropsDeclaredAsDep({foo}) { + useEffect(() => { + console.log(foo.length); + console.log(foo.slice(0)); + }, [foo]); +} + +// Valid because individual props are declared as dependencies +function ComponentWithIndividualPropsDeclaredAsDeps(props) { + useEffect(() => { + console.log(props.foo); + console.log(props.bar); + }, [props.bar, props.foo]); +} + +// Invalid because neither props or props.foo are declared as dependencies +function ComponentWithoutDeclaringPropAsDep(props) { + useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.notReactiveHook(() => { + console.log(props.foo); + }, []); // This one isn't a violation +} + +/** + * Rules of Hooks + */ +// Valid because functions can call functions. +function normalFunctionWithConditionalFunction() { + if (cond) { + doSomething(); + } +} + +// Valid because hooks can call hooks. +function useHook() { + useState(); +} +const whatever = function useHook() { + useState(); +}; +const useHook1 = () => { + useState(); +}; +let useHook2 = () => useState(); +useHook2 = () => { + useState(); +}; + +// Invalid because hooks can't be called in conditionals. +function ComponentWithConditionalHook() { + if (cond) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useConditionalHook(); + } +} + +// Invalid because hooks can't be called in loops. +function useHookInLoops() { + while (a) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook1(); + if (b) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook2(); + } + while (c) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook3(); + if (d) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook4(); + } +} diff --git a/fixtures/eslint-v6/package.json b/fixtures/eslint-v6/package.json new file mode 100644 index 0000000000000..50c92054b436f --- /dev/null +++ b/fixtures/eslint-v6/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "eslint-v6", + "dependencies": { + "eslint": "^6", + "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + }, + "scripts": { + "build": "node build.mjs && yarn", + "lint": "eslint index.js --report-unused-disable-directives" + } +} diff --git a/fixtures/eslint-v6/yarn.lock b/fixtures/eslint-v6/yarn.lock new file mode 100644 index 0000000000000..3d6dbca037b38 --- /dev/null +++ b/fixtures/eslint-v6/yarn.lock @@ -0,0 +1,865 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +acorn-jsx@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.1.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^6.0.5: + version "6.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57" + integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^4.0.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": + version "0.0.0" + uid "" + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^6: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +glob-parent@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@^4.17.14, lodash@^4.17.19: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== + +picocolors@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.5.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.1.2: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.4.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" + integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" diff --git a/fixtures/eslint-v7/.eslintrc.json b/fixtures/eslint-v7/.eslintrc.json new file mode 100644 index 0000000000000..33b2cfaacabcd --- /dev/null +++ b/fixtures/eslint-v7/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "root": true, + "extends": ["plugin:react-hooks/recommended-legacy"], + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + "react-hooks/exhaustive-deps": "error" + } +} diff --git a/fixtures/eslint-v7/README.md b/fixtures/eslint-v7/README.md new file mode 100644 index 0000000000000..ea4628b0e035a --- /dev/null +++ b/fixtures/eslint-v7/README.md @@ -0,0 +1,12 @@ +# ESLint v7 Fixture + +This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 7. + +Run the following to test. + +```sh +cd fixtures/eslint-v7 +yarn +yarn build +yarn lint +``` diff --git a/fixtures/eslint-v7/build.mjs b/fixtures/eslint-v7/build.mjs new file mode 100644 index 0000000000000..02a1bbd25e00e --- /dev/null +++ b/fixtures/eslint-v7/build.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import {exec} from 'node:child_process'; + +exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); diff --git a/fixtures/eslint-v7/index.js b/fixtures/eslint-v7/index.js new file mode 100644 index 0000000000000..6d4a7235dd257 --- /dev/null +++ b/fixtures/eslint-v7/index.js @@ -0,0 +1,143 @@ +/** + * Exhaustive Deps + */ +// Valid because dependencies are declared correctly +function Comment({comment, commentSource}) { + const currentUserID = comment.viewer.id; + const environment = RelayEnvironment.forUser(currentUserID); + const commentID = nullthrows(comment.id); + useEffect(() => { + const subscription = SubscriptionCounter.subscribeOnce( + `StoreSubscription_${commentID}`, + () => + StoreSubscription.subscribe( + environment, + { + comment_id: commentID, + }, + currentUserID, + commentSource + ) + ); + return () => subscription.dispose(); + }, [commentID, commentSource, currentUserID, environment]); +} + +// Valid because no dependencies +function UseEffectWithNoDependencies() { + const local = {}; + useEffect(() => { + console.log(local); + }); +} +function UseEffectWithEmptyDependencies() { + useEffect(() => { + const local = {}; + console.log(local); + }, []); +} + +// OK because `props` wasn't defined. +function ComponentWithNoPropsDefined() { + useEffect(() => { + console.log(props.foo); + }, []); +} + +// Valid because props are declared as a dependency +function ComponentWithPropsDeclaredAsDep({foo}) { + useEffect(() => { + console.log(foo.length); + console.log(foo.slice(0)); + }, [foo]); +} + +// Valid because individual props are declared as dependencies +function ComponentWithIndividualPropsDeclaredAsDeps(props) { + useEffect(() => { + console.log(props.foo); + console.log(props.bar); + }, [props.bar, props.foo]); +} + +// Invalid because neither props or props.foo are declared as dependencies +function ComponentWithoutDeclaringPropAsDep(props) { + useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.notReactiveHook(() => { + console.log(props.foo); + }, []); // This one isn't a violation +} + +/** + * Rules of Hooks + */ +// Valid because functions can call functions. +function normalFunctionWithConditionalFunction() { + if (cond) { + doSomething(); + } +} + +// Valid because hooks can call hooks. +function useHook() { + useState(); +} +const whatever = function useHook() { + useState(); +}; +const useHook1 = () => { + useState(); +}; +let useHook2 = () => useState(); +useHook2 = () => { + useState(); +}; + +// Invalid because hooks can't be called in conditionals. +function ComponentWithConditionalHook() { + if (cond) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useConditionalHook(); + } +} + +// Invalid because hooks can't be called in loops. +function useHookInLoops() { + while (a) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook1(); + if (b) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook2(); + } + while (c) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook3(); + if (d) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook4(); + } +} diff --git a/fixtures/eslint-v7/package.json b/fixtures/eslint-v7/package.json new file mode 100644 index 0000000000000..90c369c603e6d --- /dev/null +++ b/fixtures/eslint-v7/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "eslint-v7", + "dependencies": { + "eslint": "^7", + "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + }, + "scripts": { + "build": "node build.mjs && yarn", + "lint": "eslint index.js --report-unused-disable-directives" + } +} diff --git a/fixtures/eslint-v7/yarn.lock b/fixtures/eslint-v7/yarn.lock new file mode 100644 index 0000000000000..62f97bbc20d69 --- /dev/null +++ b/fixtures/eslint-v7/yarn.lock @@ -0,0 +1,769 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/highlight@^7.10.4": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.9.tgz#8141ce68fc73757946f983b343f1231f4691acc6" + integrity sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^7.0.2: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.0.1, debug@^4.1.1: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +enquirer@^2.3.5: + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== + dependencies: + ansi-colors "^4.1.1" + strip-ansi "^6.0.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": + version "0.0.0" + uid "" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.1.2" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fast-uri@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748" + integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.6.0, globals@^13.9.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +picocolors@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +semver@^7.2.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +table@^6.0.9: + version "6.9.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5" + integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.4.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" + integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= diff --git a/fixtures/eslint-v8/.eslintrc.json b/fixtures/eslint-v8/.eslintrc.json new file mode 100644 index 0000000000000..33b2cfaacabcd --- /dev/null +++ b/fixtures/eslint-v8/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "root": true, + "extends": ["plugin:react-hooks/recommended-legacy"], + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + "react-hooks/exhaustive-deps": "error" + } +} diff --git a/fixtures/eslint-v8/README.md b/fixtures/eslint-v8/README.md new file mode 100644 index 0000000000000..da220cef5cbdc --- /dev/null +++ b/fixtures/eslint-v8/README.md @@ -0,0 +1,12 @@ +# ESLint v8 Fixture + +This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 8. + +Run the following to test. + +```sh +cd fixtures/eslint-v8 +yarn +yarn build +yarn lint +``` diff --git a/fixtures/eslint-v8/build.mjs b/fixtures/eslint-v8/build.mjs new file mode 100644 index 0000000000000..02a1bbd25e00e --- /dev/null +++ b/fixtures/eslint-v8/build.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import {exec} from 'node:child_process'; + +exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); diff --git a/fixtures/eslint-v8/index.js b/fixtures/eslint-v8/index.js new file mode 100644 index 0000000000000..6d4a7235dd257 --- /dev/null +++ b/fixtures/eslint-v8/index.js @@ -0,0 +1,143 @@ +/** + * Exhaustive Deps + */ +// Valid because dependencies are declared correctly +function Comment({comment, commentSource}) { + const currentUserID = comment.viewer.id; + const environment = RelayEnvironment.forUser(currentUserID); + const commentID = nullthrows(comment.id); + useEffect(() => { + const subscription = SubscriptionCounter.subscribeOnce( + `StoreSubscription_${commentID}`, + () => + StoreSubscription.subscribe( + environment, + { + comment_id: commentID, + }, + currentUserID, + commentSource + ) + ); + return () => subscription.dispose(); + }, [commentID, commentSource, currentUserID, environment]); +} + +// Valid because no dependencies +function UseEffectWithNoDependencies() { + const local = {}; + useEffect(() => { + console.log(local); + }); +} +function UseEffectWithEmptyDependencies() { + useEffect(() => { + const local = {}; + console.log(local); + }, []); +} + +// OK because `props` wasn't defined. +function ComponentWithNoPropsDefined() { + useEffect(() => { + console.log(props.foo); + }, []); +} + +// Valid because props are declared as a dependency +function ComponentWithPropsDeclaredAsDep({foo}) { + useEffect(() => { + console.log(foo.length); + console.log(foo.slice(0)); + }, [foo]); +} + +// Valid because individual props are declared as dependencies +function ComponentWithIndividualPropsDeclaredAsDeps(props) { + useEffect(() => { + console.log(props.foo); + console.log(props.bar); + }, [props.bar, props.foo]); +} + +// Invalid because neither props or props.foo are declared as dependencies +function ComponentWithoutDeclaringPropAsDep(props) { + useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.notReactiveHook(() => { + console.log(props.foo); + }, []); // This one isn't a violation +} + +/** + * Rules of Hooks + */ +// Valid because functions can call functions. +function normalFunctionWithConditionalFunction() { + if (cond) { + doSomething(); + } +} + +// Valid because hooks can call hooks. +function useHook() { + useState(); +} +const whatever = function useHook() { + useState(); +}; +const useHook1 = () => { + useState(); +}; +let useHook2 = () => useState(); +useHook2 = () => { + useState(); +}; + +// Invalid because hooks can't be called in conditionals. +function ComponentWithConditionalHook() { + if (cond) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useConditionalHook(); + } +} + +// Invalid because hooks can't be called in loops. +function useHookInLoops() { + while (a) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook1(); + if (b) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook2(); + } + while (c) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook3(); + if (d) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook4(); + } +} diff --git a/fixtures/eslint-v8/package.json b/fixtures/eslint-v8/package.json new file mode 100644 index 0000000000000..620520ecdbfd4 --- /dev/null +++ b/fixtures/eslint-v8/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "eslint-v8", + "dependencies": { + "eslint": "^8", + "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + }, + "scripts": { + "build": "node build.mjs && yarn", + "lint": "eslint index.js --report-unused-disable-directives" + } +} diff --git a/fixtures/eslint-v8/yarn.lock b/fixtures/eslint-v8/yarn.lock new file mode 100644 index 0000000000000..88d04029954a4 --- /dev/null +++ b/fixtures/eslint-v8/yarn.lock @@ -0,0 +1,676 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.6.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@ungap/structured-clone@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^7.0.2: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.3.1, debug@^4.3.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": + version "0.0.0" + uid "" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.0.tgz#a82c6b7c2bb4e44766d865f07997785fecfdcb89" + integrity sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.5, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/fixtures/eslint-v9/README.md b/fixtures/eslint-v9/README.md new file mode 100644 index 0000000000000..3ea521cfa8909 --- /dev/null +++ b/fixtures/eslint-v9/README.md @@ -0,0 +1,12 @@ +# ESLint v9 Fixture + +This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 9. + +Run the following to test. + +```sh +cd fixtures/eslint-v9 +yarn +yarn build +yarn lint +``` diff --git a/fixtures/eslint-v9/build.mjs b/fixtures/eslint-v9/build.mjs new file mode 100644 index 0000000000000..02a1bbd25e00e --- /dev/null +++ b/fixtures/eslint-v9/build.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import {exec} from 'node:child_process'; + +exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); diff --git a/fixtures/eslint-v9/eslint.config.mjs b/fixtures/eslint-v9/eslint.config.mjs new file mode 100644 index 0000000000000..ffc8375265c2b --- /dev/null +++ b/fixtures/eslint-v9/eslint.config.mjs @@ -0,0 +1,21 @@ +import * as reactHooks from 'eslint-plugin-react-hooks'; + +export default [ + { + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + reactHooks.configs['recommended-latest'], + { + rules: { + 'react-hooks/exhaustive-deps': 'error', + }, + }, +]; diff --git a/fixtures/eslint-v9/index.js b/fixtures/eslint-v9/index.js new file mode 100644 index 0000000000000..6d4a7235dd257 --- /dev/null +++ b/fixtures/eslint-v9/index.js @@ -0,0 +1,143 @@ +/** + * Exhaustive Deps + */ +// Valid because dependencies are declared correctly +function Comment({comment, commentSource}) { + const currentUserID = comment.viewer.id; + const environment = RelayEnvironment.forUser(currentUserID); + const commentID = nullthrows(comment.id); + useEffect(() => { + const subscription = SubscriptionCounter.subscribeOnce( + `StoreSubscription_${commentID}`, + () => + StoreSubscription.subscribe( + environment, + { + comment_id: commentID, + }, + currentUserID, + commentSource + ) + ); + return () => subscription.dispose(); + }, [commentID, commentSource, currentUserID, environment]); +} + +// Valid because no dependencies +function UseEffectWithNoDependencies() { + const local = {}; + useEffect(() => { + console.log(local); + }); +} +function UseEffectWithEmptyDependencies() { + useEffect(() => { + const local = {}; + console.log(local); + }, []); +} + +// OK because `props` wasn't defined. +function ComponentWithNoPropsDefined() { + useEffect(() => { + console.log(props.foo); + }, []); +} + +// Valid because props are declared as a dependency +function ComponentWithPropsDeclaredAsDep({foo}) { + useEffect(() => { + console.log(foo.length); + console.log(foo.slice(0)); + }, [foo]); +} + +// Valid because individual props are declared as dependencies +function ComponentWithIndividualPropsDeclaredAsDeps(props) { + useEffect(() => { + console.log(props.foo); + console.log(props.bar); + }, [props.bar, props.foo]); +} + +// Invalid because neither props or props.foo are declared as dependencies +function ComponentWithoutDeclaringPropAsDep(props) { + useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useEffect(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useCallback(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.useMemo(() => { + console.log(props.foo); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + React.notReactiveHook(() => { + console.log(props.foo); + }, []); // This one isn't a violation +} + +/** + * Rules of Hooks + */ +// Valid because functions can call functions. +function normalFunctionWithConditionalFunction() { + if (cond) { + doSomething(); + } +} + +// Valid because hooks can call hooks. +function useHook() { + useState(); +} +const whatever = function useHook() { + useState(); +}; +const useHook1 = () => { + useState(); +}; +let useHook2 = () => useState(); +useHook2 = () => { + useState(); +}; + +// Invalid because hooks can't be called in conditionals. +function ComponentWithConditionalHook() { + if (cond) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useConditionalHook(); + } +} + +// Invalid because hooks can't be called in loops. +function useHookInLoops() { + while (a) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook1(); + if (b) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook2(); + } + while (c) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook3(); + if (d) return; + // eslint-disable-next-line react-hooks/rules-of-hooks + useHook4(); + } +} diff --git a/fixtures/eslint-v9/package.json b/fixtures/eslint-v9/package.json new file mode 100644 index 0000000000000..e8ae91a0613e1 --- /dev/null +++ b/fixtures/eslint-v9/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "eslint-v9", + "dependencies": { + "eslint": "^9", + "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + }, + "scripts": { + "build": "node build.mjs && yarn", + "lint": "eslint index.js --report-unused-disable-directives" + } +} diff --git a/fixtures/eslint-v9/yarn.lock b/fixtures/eslint-v9/yarn.lock new file mode 100644 index 0000000000000..5a1a246827f6f --- /dev/null +++ b/fixtures/eslint-v9/yarn.lock @@ -0,0 +1,579 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" + integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" + integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/core@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" + integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.20.0": + version "9.20.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" + integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== + +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" + integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== + dependencies: + "@eslint/core" "^0.10.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" + integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== + +"@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.3.1, debug@^4.3.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": + version "0.0.0" + uid "" + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9: + version "9.20.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" + integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.11.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.20.0" + "@eslint/plugin-kit" "^0.2.5" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27" + integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/fixtures/eslint/README.md b/fixtures/eslint/README.md deleted file mode 100644 index 28ab22551372d..0000000000000 --- a/fixtures/eslint/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# ESLint Playground Fixture - -This is an internal playground for quick iteration on our lint rules inside an IDE like VSCode. - -See instructions in `./index.js` in this directory. - -![Demo](https://duaw26jehqd4r.cloudfront.net/items/2Z390a31003O0l0o0e3O/Screen%20Recording%202019-01-16%20at%2010.29%20PM.gif?v=d6856125) \ No newline at end of file diff --git a/fixtures/eslint/index.js b/fixtures/eslint/index.js deleted file mode 100644 index d53415310560d..0000000000000 --- a/fixtures/eslint/index.js +++ /dev/null @@ -1,26 +0,0 @@ -// This is a testing playground for our lint rules. - -// 1. Run yarn && yarn start -// 2. "File > Add Folder to Workspace" this specific folder in VSCode with ESLint extension -// 3. Changes to the rule source should get picked up without restarting ESLint server - -function Comment({comment, commentSource}) { - const currentUserID = comment.viewer.id; - const environment = RelayEnvironment.forUser(currentUserID); - const commentID = nullthrows(comment.id); - useEffect(() => { - const subscription = SubscriptionCounter.subscribeOnce( - `StoreSubscription_${commentID}`, - () => - StoreSubscription.subscribe( - environment, - { - comment_id: commentID, - }, - currentUserID, - commentSource - ) - ); - return () => subscription.dispose(); - }, [commentID, commentSource, currentUserID, environment]); -} diff --git a/fixtures/eslint/package.json b/fixtures/eslint/package.json deleted file mode 100644 index 04f8d6b98be99..0000000000000 --- a/fixtures/eslint/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "private": true, - "name": "eslint-playground", - "dependencies": { - "eslint": "4.1.0", - "eslint-plugin-react-hooks": "link:./proxy" - }, - "scripts": { - "start": "./watch.sh", - "lint": "eslint index.js" - } -} diff --git a/fixtures/eslint/proxy/index.js b/fixtures/eslint/proxy/index.js deleted file mode 100644 index f54e672d21126..0000000000000 --- a/fixtures/eslint/proxy/index.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -// This file is a proxy for our rule definition that will -// load the latest built version on every check. This makes -// it convenient to test inside IDEs (which would otherwise -// load a version of our rule once and never restart the server). -// See instructions in ../index.js playground. - -let build; -reload(); - -function reload() { - for (let id in require.cache) { - if (/eslint-plugin-react-hooks/.test(id)) { - delete require.cache[id]; - } - } - // Point to the built version. - build = require('../../../build/oss-experimental/eslint-plugin-react-hooks'); -} - -let rules = {}; -for (let key in build.rules) { - if (build.rules.hasOwnProperty(key)) { - rules[key] = Object.assign({}, build.rules, { - create() { - // Reload changes to the built rule - reload(); - return build.rules[key].create.apply(this, arguments); - }, - }); - } -} - -module.exports = {rules}; diff --git a/fixtures/eslint/proxy/package.json b/fixtures/eslint/proxy/package.json deleted file mode 100644 index 1461c2d43f5f1..0000000000000 --- a/fixtures/eslint/proxy/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "private": true, - "version": "0.0.0" -} \ No newline at end of file diff --git a/fixtures/eslint/watch.sh b/fixtures/eslint/watch.sh deleted file mode 100755 index 0ca6359c03516..0000000000000 --- a/fixtures/eslint/watch.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -(cd ../.. && yarn build eslint --type=NODE_DEV) -(cd ../.. && watchman-make --make 'yarn build eslint --type=NODE_DEV' -p 'packages/eslint-plugin-*/**/*' -t ignored) diff --git a/fixtures/eslint/yarn.lock b/fixtures/eslint/yarn.lock deleted file mode 100644 index 7a557036da3aa..0000000000000 --- a/fixtures/eslint/yarn.lock +++ /dev/null @@ -1,853 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -acorn-jsx@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= - dependencies: - acorn "^3.0.4" - -acorn@^3.0.4: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= - -acorn@^5.5.0: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== - -ajv-keywords@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" - integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= - -ajv@^6.0.1: - version "6.7.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96" - integrity sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" - integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= - dependencies: - callsites "^0.2.0" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.1.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= - -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= - dependencies: - restore-cursor "^2.0.0" - -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -debug@^2.6.8: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -doctrine@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -"eslint-plugin-react-hooks@link:./proxy": - version "0.0.0" - uid "" - -eslint-scope@^3.7.1: - version "3.7.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" - integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.1.0.tgz#bbb55a28220ee08b69da9554d45a6b2ebfd7d913" - integrity sha1-u7VaKCIO4Itp2pVU1FprLr/X2RM= - dependencies: - babel-code-frame "^6.22.0" - chalk "^1.1.3" - concat-stream "^1.6.0" - debug "^2.6.8" - doctrine "^2.0.0" - eslint-scope "^3.7.1" - espree "^3.4.3" - esquery "^1.0.0" - estraverse "^4.2.0" - esutils "^2.0.2" - file-entry-cache "^2.0.0" - glob "^7.1.2" - globals "^9.17.0" - ignore "^3.3.3" - imurmurhash "^0.1.4" - inquirer "^3.0.6" - is-my-json-valid "^2.16.0" - is-resolvable "^1.0.0" - js-yaml "^3.8.4" - json-stable-stringify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.4" - minimatch "^3.0.2" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^4.0.0" - progress "^2.0.0" - require-uncached "^1.0.3" - strip-json-comments "~2.0.1" - table "^4.0.1" - text-table "~0.2.0" - -espree@^3.4.3: - version "3.5.4" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" - integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== - dependencies: - acorn "^5.5.0" - acorn-jsx "^3.0.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" - integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== - dependencies: - estraverse "^4.0.0" - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" - integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= - -esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= - -external-editor@^2.0.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -fast-levenshtein@~2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== - dependencies: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -generate-function@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" - integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== - dependencies: - is-property "^1.0.2" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= - dependencies: - is-property "^1.0.0" - -glob@^7.1.2, glob@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^9.17.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== - -graceful-fs@^4.1.2: - version "4.1.15" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" - integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -iconv-lite@^0.4.17: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore@^3.3.3: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -inquirer@^3.0.6: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-my-ip-valid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" - integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== - -is-my-json-valid@^2.16.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175" - integrity sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q== - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-property@^1.0.0, is-property@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -js-yaml@^3.8.4: - version "3.12.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" - integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= - -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lodash@^4.17.4, lodash@^4.3.0: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -minimatch@^3.0.2, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - -optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -pluralize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762" - integrity sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I= - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -readable-stream@^2.2.2: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -require-uncached@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -rimraf@~2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= - dependencies: - is-promise "^2.1.0" - -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" - integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" - integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== - dependencies: - is-fullwidth-code-point "^2.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -table@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" - integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg== - dependencies: - ajv "^6.0.1" - ajv-keywords "^3.0.0" - chalk "^2.1.0" - lodash "^4.17.4" - slice-ansi "1.0.0" - string-width "^2.1.1" - -text-table@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= - dependencies: - mkdirp "^0.5.1" - -xtend@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= From 5adf40208f4a2f56bda5c059d18ce578c5091dab Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 16 Feb 2025 13:10:54 -0600 Subject: [PATCH 32/73] feat(eslint-plugin-react-hooks): convert to typescript and package type declarations (#32240) ## Summary This change converts the eslint hooks plugin to typescript, which also allows us to include type declarations in the package, for those using [typescript eslint configs](https://eslint.org/blog/2025/01/eslint-v9.18.0-released/#stable-typescript-configuration-file-support). ### Constituent changes that should land before this one - [x] ~https://github.com/facebook/react/pull/32276~ - [x] https://github.com/facebook/react/pull/32279 - [x] https://github.com/facebook/react/pull/32283 - [x] https://github.com/facebook/react/pull/32393 - [x] https://github.com/facebook/react/pull/32396 Closes #30119 --------- Co-authored-by: Lauren Tan --- .../eslint-plugin-react-hooks/babel.config.js | 8 + packages/eslint-plugin-react-hooks/index.js | 9 +- .../eslint-plugin-react-hooks/jest.config.js | 8 + .../eslint-plugin-react-hooks/npm/index.d.ts | 1 + .../eslint-plugin-react-hooks/package.json | 11 +- .../{ExhaustiveDeps.js => ExhaustiveDeps.ts} | 631 +++++++++++------- .../src/{RulesOfHooks.js => RulesOfHooks.ts} | 145 ++-- .../eslint-plugin-react-hooks/src/index.js | 61 -- .../eslint-plugin-react-hooks/src/index.ts | 64 ++ .../src/types/estree.d.ts | 2 + .../eslint-plugin-react-hooks/tsconfig.json | 1 - .../eslint-plugin-react-hooks/tsup.config.ts | 9 - scripts/rollup/bundles.js | 14 +- yarn.lock | 467 +------------ 14 files changed, 574 insertions(+), 857 deletions(-) create mode 100644 packages/eslint-plugin-react-hooks/babel.config.js create mode 100644 packages/eslint-plugin-react-hooks/jest.config.js create mode 100644 packages/eslint-plugin-react-hooks/npm/index.d.ts rename packages/eslint-plugin-react-hooks/src/{ExhaustiveDeps.js => ExhaustiveDeps.ts} (78%) rename packages/eslint-plugin-react-hooks/src/{RulesOfHooks.js => RulesOfHooks.ts} (85%) delete mode 100644 packages/eslint-plugin-react-hooks/src/index.js create mode 100644 packages/eslint-plugin-react-hooks/src/index.ts delete mode 100644 packages/eslint-plugin-react-hooks/tsup.config.ts diff --git a/packages/eslint-plugin-react-hooks/babel.config.js b/packages/eslint-plugin-react-hooks/babel.config.js new file mode 100644 index 0000000000000..3b947a7163bcb --- /dev/null +++ b/packages/eslint-plugin-react-hooks/babel.config.js @@ -0,0 +1,8 @@ +/** + * This file is purely being used for local jest runs, and doesn't participate in the build process. + */ +'use strict'; + +module.exports = { + extends: '../../babel.config-ts.js', +}; diff --git a/packages/eslint-plugin-react-hooks/index.js b/packages/eslint-plugin-react-hooks/index.js index 754dc9f9c7f23..ce26a10c31518 100644 --- a/packages/eslint-plugin-react-hooks/index.js +++ b/packages/eslint-plugin-react-hooks/index.js @@ -1,8 +1 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -export * from './src/index'; +module.exports = require('./src/index.ts'); diff --git a/packages/eslint-plugin-react-hooks/jest.config.js b/packages/eslint-plugin-react-hooks/jest.config.js new file mode 100644 index 0000000000000..a7b91c3ef1cbe --- /dev/null +++ b/packages/eslint-plugin-react-hooks/jest.config.js @@ -0,0 +1,8 @@ +'use strict'; + +process.env.NODE_ENV = 'development'; + +module.exports = { + setupFiles: [require.resolve('../../scripts/jest/setupEnvironment.js')], + moduleFileExtensions: ['ts', 'js', 'json'], +}; diff --git a/packages/eslint-plugin-react-hooks/npm/index.d.ts b/packages/eslint-plugin-react-hooks/npm/index.d.ts new file mode 100644 index 0000000000000..62ca164ec2173 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/npm/index.d.ts @@ -0,0 +1 @@ +export * from './cjs/eslint-plugin-react-hooks'; diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index b45609362faac..d3717b9dee61c 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -10,8 +10,9 @@ "files": [ "LICENSE", "README.md", + "cjs", "index.js", - "cjs" + "index.d.ts" ], "keywords": [ "eslint", @@ -19,10 +20,16 @@ "eslintplugin", "react" ], + "scripts": { + "test": "jest", + "typecheck": "tsc --noEmit" + }, "license": "MIT", "bugs": { "url": "https://github.com/facebook/react/issues" }, + "main": "./index.js", + "types": "./index.d.ts", "engines": { "node": ">=10" }, @@ -32,6 +39,7 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.11.4", + "@babel/preset-typescript": "^7.26.0", "@tsconfig/strictest": "^2.0.5", "@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0", "@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0", @@ -45,7 +53,6 @@ "eslint-v7": "npm:eslint@^7.7.0", "eslint-v9": "npm:eslint@^9.0.0", "jest": "^29.5.0", - "tsup": "^8.3.5", "typescript": "^5.4.3" } } diff --git a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts similarity index 78% rename from packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js rename to packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts index b2818327ff9e4..9f3d4d4db9004 100644 --- a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js +++ b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts @@ -4,12 +4,41 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - /* eslint-disable no-for-of-loops/no-for-of-loops */ +import type {Rule, Scope} from 'eslint'; +import type { + ArrayExpression, + ArrowFunctionExpression, + CallExpression, + Expression, + FunctionDeclaration, + FunctionExpression, + Identifier, + Node, + Pattern, + PrivateIdentifier, + Super, + VariableDeclarator, +} from 'estree'; + +type DeclaredDependency = { + key: string; + node: Node; +}; -'use strict'; +type Dependency = { + isStable: boolean; + references: Scope.Reference[]; +}; + +type DependencyTreeNode = { + isUsed: boolean; // True if used in code + isSatisfiedRecursively: boolean; // True if specified in deps + isSubtreeUsed: boolean; // True if something deeper is used by code + children: Map; // Nodes for properties +}; -export default { +const rule = { meta: { type: 'suggestion', docs: { @@ -36,7 +65,7 @@ export default { }, ], }, - create(context) { + create(context: Rule.RuleContext) { // Parse the `additionalHooks` regex. const additionalHooks = context.options && @@ -45,7 +74,7 @@ export default { ? new RegExp(context.options[0].additionalHooks) : undefined; - const enableDangerousAutofixThisMayCauseInfiniteLoops = + const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean = (context.options && context.options[0] && context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) || @@ -56,11 +85,15 @@ export default { enableDangerousAutofixThisMayCauseInfiniteLoops, }; - function reportProblem(problem) { + function reportProblem(problem: Rule.ReportDescriptor) { if (enableDangerousAutofixThisMayCauseInfiniteLoops) { // Used to enable legacy behavior. Dangerous. // Keep this as an option until major IDEs upgrade (including VSCode FB ESLint extension). - if (Array.isArray(problem.suggest) && problem.suggest.length > 0) { + if ( + Array.isArray(problem.suggest) && + problem.suggest.length > 0 && + problem.suggest[0] + ) { problem.fix = problem.suggest[0].fix; } } @@ -68,15 +101,15 @@ export default { } /** - * SourceCode#getText that also works down to ESLint 3.0.0 + * SourceCode that also works down to ESLint 3.0.0 */ - const getSource = - typeof context.getSource === 'function' - ? node => { - return context.getSource(node); + const getSourceCode = + typeof context.getSourceCode === 'function' + ? () => { + return context.getSourceCode(); } - : node => { - return context.sourceCode.getText(node); + : () => { + return context.sourceCode; }; /** * SourceCode#getScope that also works down to ESLint 3.0.0 @@ -86,24 +119,34 @@ export default { ? () => { return context.getScope(); } - : node => { + : (node: Node) => { return context.sourceCode.getScope(node); }; - const scopeManager = context.getSourceCode().scopeManager; + const scopeManager = getSourceCode().scopeManager; // Should be shared between visitors. - const setStateCallSites = new WeakMap(); - const stateVariables = new WeakSet(); - const stableKnownValueCache = new WeakMap(); - const functionWithoutCapturedValueCache = new WeakMap(); - const useEffectEventVariables = new WeakSet(); - function memoizeWithWeakMap(fn, map) { - return function (arg) { + const setStateCallSites = new WeakMap< + Expression | Super, + Pattern | null | undefined + >(); + const stateVariables = new WeakSet(); + const stableKnownValueCache = new WeakMap(); + const functionWithoutCapturedValueCache = new WeakMap< + Scope.Variable, + boolean + >(); + const useEffectEventVariables = new WeakSet(); + + function memoizeWithWeakMap( + fn: (resolved: Scope.Variable) => boolean, + map: WeakMap, + ) { + return function (arg: Scope.Variable): boolean { if (map.has(arg)) { // to verify cache hits: // console.log(arg.name) - return map.get(arg); + return map.get(arg)!; } const result = fn(arg); map.set(arg, result); @@ -114,12 +157,12 @@ export default { * Visitor for both function expressions and arrow function expressions. */ function visitFunctionWithDependencies( - node, - declaredDependenciesNode, - reactiveHook, - reactiveHookName, - isEffect, - ) { + node: ArrowFunctionExpression | FunctionDeclaration | FunctionExpression, + declaredDependenciesNode: Node | undefined, + reactiveHook: Node, + reactiveHookName: string, + isEffect: boolean, + ): void { if (isEffect && node.async) { reportProblem({ node: node, @@ -140,6 +183,9 @@ export default { // Get the current scope. const scope = scopeManager.acquire(node); + if (!scope) { + return; + } // Find all our "pure scopes". On every re-render of a component these // pure scopes may have changes to the variables declared within. So all @@ -150,7 +196,7 @@ export default { // scope. We can't enforce this in a lint so we trust that all variables // declared outside of pure scope are indeed frozen. const pureScopes = new Set(); - let componentScope = null; + let componentScope: Scope.Scope | null = null; { let currentScope = scope.upper; while (currentScope) { @@ -186,7 +232,7 @@ export default { // const onStuff = useEffectEvent(() => {}) // ^^^ true for this reference // False for everything else. - function isStableKnownHookValue(resolved) { + function isStableKnownHookValue(resolved: Scope.Variable): boolean { if (!isArray(resolved.defs)) { return false; } @@ -195,10 +241,11 @@ export default { return false; } // Look for `let stuff = ...` - if (def.node.type !== 'VariableDeclarator') { + const defNode: VariableDeclarator = def.node; + if (defNode.type !== 'VariableDeclarator') { return false; } - let init = def.node.init; + let init = defNode.init; if (init == null) { return false; } @@ -207,8 +254,8 @@ export default { } // Detect primitive constants // const foo = 42 - let declaration = def.node.parent; - if (declaration == null) { + let declaration = defNode.parent; + if (declaration == null && componentScope) { // This might happen if variable is declared after the callback. // In that case ESLint won't set up .parent refs. // So we'll set them up manually. @@ -219,6 +266,8 @@ export default { } } if ( + declaration && + 'kind' in declaration && declaration.kind === 'const' && init.type === 'Literal' && (typeof init.value === 'string' || @@ -233,10 +282,11 @@ export default { if (init.type !== 'CallExpression') { return false; } - let callee = init.callee; + let callee: Expression | PrivateIdentifier | Super = init.callee; // Step into `= React.something` initializer. if ( callee.type === 'MemberExpression' && + 'name' in callee.object && callee.object.name === 'React' && callee.property != null && !callee.computed @@ -246,7 +296,8 @@ export default { if (callee.type !== 'Identifier') { return false; } - const id = def.node.id; + const definitionNode: VariableDeclarator = def.node; + const id = definitionNode.id; const {name} = callee; if (name === 'useRef' && id.type === 'Identifier') { // useRef() return value is stable. @@ -256,6 +307,7 @@ export default { id.type === 'Identifier' ) { for (const ref of resolved.references) { + // @ts-expect-error These types are not compatible (Reference and Identifier) if (ref !== id) { useEffectEventVariables.add(ref.identifier); } @@ -278,17 +330,14 @@ export default { if (name === 'useState') { const references = resolved.references; let writeCount = 0; - for (let i = 0; i < references.length; i++) { - if (references[i].isWrite()) { + for (const reference of references) { + if (reference.isWrite()) { writeCount++; } if (writeCount > 1) { return false; } - setStateCallSites.set( - references[i].identifier, - id.elements[0], - ); + setStateCallSites.set(reference.identifier, id.elements[0]); } } // Setter is stable. @@ -296,8 +345,8 @@ export default { } else if (id.elements[0] === resolved.identifiers[0]) { if (name === 'useState') { const references = resolved.references; - for (let i = 0; i < references.length; i++) { - stateVariables.add(references[i].identifier); + for (const reference of references) { + stateVariables.add(reference.identifier); } } // State variable itself is dynamic. @@ -323,7 +372,9 @@ export default { } // Some are just functions that don't reference anything dynamic. - function isFunctionWithoutCapturedValues(resolved) { + function isFunctionWithoutCapturedValues( + resolved: Scope.Variable, + ): boolean { if (!isArray(resolved.defs)) { return false; } @@ -336,12 +387,10 @@ export default { } // Search the direct component subscopes for // top-level function definitions matching this reference. - const fnNode = def.node; - const childScopes = componentScope.childScopes; + const fnNode: Node = def.node; + const childScopes = componentScope?.childScopes || []; let fnScope = null; - let i; - for (i = 0; i < childScopes.length; i++) { - const childScope = childScopes[i]; + for (const childScope of childScopes) { const childScopeBlock = childScope.block; if ( // function handleChange() {} @@ -362,8 +411,7 @@ export default { } // Does this function capture any values // that are in pure scopes (aka render)? - for (i = 0; i < fnScope.through.length; i++) { - const ref = fnScope.through[i]; + for (const ref of fnScope.through) { if (ref.resolved == null) { continue; } @@ -392,15 +440,21 @@ export default { ); // These are usually mistaken. Collect them. - const currentRefsInEffectCleanup = new Map(); + const currentRefsInEffectCleanup = new Map< + string, + { + reference: Scope.Reference; + dependencyNode: Identifier; + } + >(); // Is this reference inside a cleanup function for this effect node? // We can check by traversing scopes upwards from the reference, and checking // if the last "return () => " we encounter is located directly inside the effect. - function isInsideEffectCleanup(reference) { - let curScope = reference.from; + function isInsideEffectCleanup(reference: Scope.Reference): boolean { + let curScope: Scope.Scope | null = reference.from; let isInReturnedFunction = false; - while (curScope.block !== node) { + while (curScope && curScope.block !== node) { if (curScope.type === 'function') { isInReturnedFunction = curScope.block.parent != null && @@ -413,11 +467,11 @@ export default { // Get dependencies from all our resolved references in pure scopes. // Key is dependency string, value is whether it's stable. - const dependencies = new Map(); - const optionalChains = new Map(); + const dependencies = new Map(); + const optionalChains = new Map(); gatherDependenciesRecursively(scope); - function gatherDependenciesRecursively(currentScope) { + function gatherDependenciesRecursively(currentScope: Scope.Scope): void { for (const reference of currentScope.references) { // If this reference is not resolved or it is not declared in a pure // scope then we don't care about this reference. @@ -434,6 +488,9 @@ export default { node, reference.identifier, ); + if (referenceNode == null) { + continue; + } const dependencyNode = getDependency(referenceNode); const dependency = analyzePropertyChain( dependencyNode, @@ -446,8 +503,8 @@ export default { isEffect && // ... and this look like accessing .current... dependencyNode.type === 'Identifier' && - (dependencyNode.parent.type === 'MemberExpression' || - dependencyNode.parent.type === 'OptionalMemberExpression') && + (dependencyNode.parent?.type === 'MemberExpression' || + dependencyNode.parent?.type === 'OptionalMemberExpression') && !dependencyNode.parent.computed && dependencyNode.parent.property.type === 'Identifier' && dependencyNode.parent.property.name === 'current' && @@ -461,8 +518,8 @@ export default { } if ( - dependencyNode.parent.type === 'TSTypeQuery' || - dependencyNode.parent.type === 'TSTypeReference' + dependencyNode.parent?.type === 'TSTypeQuery' || + dependencyNode.parent?.type === 'TSTypeReference' ) { continue; } @@ -472,10 +529,11 @@ export default { continue; } // Ignore references to the function itself as it's not defined yet. - if (def.node != null && def.node.init === node.parent) { + if (def.node && def.node.init === node.parent) { continue; } // Ignore Flow type parameters + // @ts-expect-error We don't have flow types if (def.type === 'TypeParameter') { continue; } @@ -492,7 +550,7 @@ export default { references: [reference], }); } else { - dependencies.get(dependency).references.push(reference); + dependencies.get(dependency)?.references.push(reference); } } @@ -504,12 +562,12 @@ export default { // Warn about accessing .current in cleanup effects. currentRefsInEffectCleanup.forEach( ({reference, dependencyNode}, dependency) => { - const references = reference.resolved.references; + const references = reference.resolved?.references || []; // Is React managing this ref or us? // Let's see if we can find a .current assignment. let foundCurrentAssignment = false; - for (let i = 0; i < references.length; i++) { - const {identifier} = references[i]; + for (const reference of references) { + const {identifier} = reference; const {parent} = identifier; if ( parent != null && @@ -520,7 +578,7 @@ export default { parent.property.type === 'Identifier' && parent.property.name === 'current' && // ref.current = - parent.parent.type === 'AssignmentExpression' && + parent.parent?.type === 'AssignmentExpression' && parent.parent.left === parent ) { foundCurrentAssignment = true; @@ -532,6 +590,7 @@ export default { return; } reportProblem({ + // @ts-expect-error We can do better here (dependencyNode.parent has not been type narrowed) node: dependencyNode.parent.property, message: `The ref value '${dependency}.current' will likely have ` + @@ -545,8 +604,8 @@ export default { // Warn about assigning to variables in the outer scope. // Those are usually bugs. - const staleAssignments = new Set(); - function reportStaleAssignment(writeExpr, key) { + const staleAssignments = new Set(); + function reportStaleAssignment(writeExpr: Node, key: string): void { if (staleAssignments.has(key)) { return; } @@ -555,16 +614,16 @@ export default { node: writeExpr, message: `Assignments to the '${key}' variable from inside React Hook ` + - `${getSource(reactiveHook)} will be lost after each ` + + `${getSourceCode().getText(reactiveHook)} will be lost after each ` + `render. To preserve the value over time, store it in a useRef ` + `Hook and keep the mutable value in the '.current' property. ` + `Otherwise, you can move this variable directly inside ` + - `${getSource(reactiveHook)}.`, + `${getSourceCode().getText(reactiveHook)}.`, }); } // Remember which deps are stable and report bad usage first. - const stableDependencies = new Set(); + const stableDependencies = new Set(); dependencies.forEach(({isStable, references}, key) => { if (isStable) { stableDependencies.add(key); @@ -584,8 +643,8 @@ export default { if (!declaredDependenciesNode) { // Check if there are any top-level setState() calls. // Those tend to lead to infinite loops. - let setStateInsideEffectWithoutDeps = null; - dependencies.forEach(({isStable, references}, key) => { + let setStateInsideEffectWithoutDeps: string | null = null; + dependencies.forEach(({references}, key) => { if (setStateInsideEffectWithoutDeps) { return; } @@ -600,11 +659,11 @@ export default { return; } - let fnScope = reference.from; - while (fnScope.type !== 'function') { + let fnScope: Scope.Scope | null = reference.from; + while (fnScope && fnScope.type !== 'function') { fnScope = fnScope.upper; } - const isDirectlyInsideEffect = fnScope.block === node; + const isDirectlyInsideEffect = fnScope?.block === node; if (isDirectlyInsideEffect) { // TODO: we could potentially ignore early returns. setStateInsideEffectWithoutDeps = key; @@ -616,7 +675,7 @@ export default { dependencies, declaredDependencies: [], stableDependencies, - externalDependencies: new Set(), + externalDependencies: new Set(), isEffect: true, }); reportProblem({ @@ -645,8 +704,8 @@ export default { return; } - const declaredDependencies = []; - const externalDependencies = new Set(); + const declaredDependencies: DeclaredDependency[] = []; + const externalDependencies = new Set(); const isArrayExpression = declaredDependenciesNode.type === 'ArrayExpression'; const isTSAsArrayExpression = @@ -660,7 +719,7 @@ export default { reportProblem({ node: declaredDependenciesNode, message: - `React Hook ${getSource(reactiveHook)} was passed a ` + + `React Hook ${getSourceCode().getText(reactiveHook)} was passed a ` + 'dependency list that is not an array literal. This means we ' + "can't statically verify whether you've passed the correct " + 'dependencies.', @@ -670,108 +729,117 @@ export default { ? declaredDependenciesNode.expression : declaredDependenciesNode; - arrayExpression.elements.forEach(declaredDependencyNode => { - // Skip elided elements. - if (declaredDependencyNode === null) { - return; - } - // If we see a spread element then add a special warning. - if (declaredDependencyNode.type === 'SpreadElement') { - reportProblem({ - node: declaredDependencyNode, - message: - `React Hook ${getSource(reactiveHook)} has a spread ` + - "element in its dependency array. This means we can't " + - "statically verify whether you've passed the " + - 'correct dependencies.', - }); - return; - } - if (useEffectEventVariables.has(declaredDependencyNode)) { - reportProblem({ - node: declaredDependencyNode, - message: - 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + - `Remove \`${getSource( - declaredDependencyNode, - )}\` from the list.`, - suggest: [ - { - desc: `Remove the dependency \`${getSource( + (arrayExpression as ArrayExpression).elements.forEach( + declaredDependencyNode => { + // Skip elided elements. + if (declaredDependencyNode === null) { + return; + } + // If we see a spread element then add a special warning. + if (declaredDependencyNode.type === 'SpreadElement') { + reportProblem({ + node: declaredDependencyNode, + message: + `React Hook ${getSourceCode().getText(reactiveHook)} has a spread ` + + "element in its dependency array. This means we can't " + + "statically verify whether you've passed the " + + 'correct dependencies.', + }); + return; + } + if (useEffectEventVariables.has(declaredDependencyNode)) { + reportProblem({ + node: declaredDependencyNode, + message: + 'Functions returned from `useEffectEvent` must not be included in the dependency array. ' + + `Remove \`${getSourceCode().getText( declaredDependencyNode, - )}\``, - fix(fixer) { - return fixer.removeRange(declaredDependencyNode.range); + )}\` from the list.`, + suggest: [ + { + desc: `Remove the dependency \`${getSourceCode().getText( + declaredDependencyNode, + )}\``, + fix(fixer) { + return fixer.removeRange(declaredDependencyNode.range!); + }, }, - }, - ], - }); - } - // Try to normalize the declared dependency. If we can't then an error - // will be thrown. We will catch that error and report an error. - let declaredDependency; - try { - declaredDependency = analyzePropertyChain( - declaredDependencyNode, - null, - ); - } catch (error) { - if (/Unsupported node type/.test(error.message)) { - if (declaredDependencyNode.type === 'Literal') { - if (dependencies.has(declaredDependencyNode.value)) { - reportProblem({ - node: declaredDependencyNode, - message: - `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + - `because it never changes. ` + - `Did you mean to include ${declaredDependencyNode.value} in the array instead?`, - }); + ], + }); + } + // Try to normalize the declared dependency. If we can't then an error + // will be thrown. We will catch that error and report an error. + let declaredDependency; + try { + declaredDependency = analyzePropertyChain( + declaredDependencyNode, + null, + ); + } catch (error: unknown) { + if ( + error instanceof Error && + /Unsupported node type/.test(error.message) + ) { + if (declaredDependencyNode.type === 'Literal') { + if ( + declaredDependencyNode.value && + dependencies.has(declaredDependencyNode.value as string) + ) { + reportProblem({ + node: declaredDependencyNode, + message: + `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + + `because it never changes. ` + + `Did you mean to include ${declaredDependencyNode.value} in the array instead?`, + }); + } else { + reportProblem({ + node: declaredDependencyNode, + message: + `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + + 'because it never changes. You can safely remove it.', + }); + } } else { reportProblem({ node: declaredDependencyNode, message: - `The ${declaredDependencyNode.raw} literal is not a valid dependency ` + - 'because it never changes. You can safely remove it.', + `React Hook ${getSourceCode().getText(reactiveHook)} has a ` + + `complex expression in the dependency array. ` + + 'Extract it to a separate variable so it can be statically checked.', }); } + + return; } else { - reportProblem({ - node: declaredDependencyNode, - message: - `React Hook ${getSource(reactiveHook)} has a ` + - `complex expression in the dependency array. ` + - 'Extract it to a separate variable so it can be statically checked.', - }); + throw error; } - - return; - } else { - throw error; } - } - let maybeID = declaredDependencyNode; - while ( - maybeID.type === 'MemberExpression' || - maybeID.type === 'OptionalMemberExpression' || - maybeID.type === 'ChainExpression' - ) { - maybeID = maybeID.object || maybeID.expression.object; - } - const isDeclaredInComponent = !componentScope.through.some( - ref => ref.identifier === maybeID, - ); + let maybeID = declaredDependencyNode; + while ( + maybeID.type === 'MemberExpression' || + maybeID.type === 'OptionalMemberExpression' || + maybeID.type === 'ChainExpression' + ) { + // @ts-expect-error This can be done better + maybeID = maybeID.object || maybeID.expression.object; + } + const isDeclaredInComponent = !componentScope.through.some( + ref => ref.identifier === maybeID, + ); - // Add the dependency to our declared dependency map. - declaredDependencies.push({ - key: declaredDependency, - node: declaredDependencyNode, - }); + // Add the dependency to our declared dependency map. + declaredDependencies.push({ + key: declaredDependency, + node: declaredDependencyNode, + }); - if (!isDeclaredInComponent) { - externalDependencies.add(declaredDependency); - } - }); + if (!isDeclaredInComponent) { + externalDependencies.add(declaredDependency); + } + }, + ); } const { @@ -824,10 +892,10 @@ export default { const message = `The '${construction.name.name}' ${depType} ${causation} the dependencies of ` + - `${reactiveHookName} Hook (at line ${declaredDependenciesNode.loc.start.line}) ` + + `${reactiveHookName} Hook (at line ${declaredDependenciesNode.loc?.start.line}) ` + `change on every render. ${advice}`; - let suggest; + let suggest: Rule.ReportDescriptor['suggest']; // Only handle the simple case of variable assignments. // Wrapping function declarations can mess up hoisting. if ( @@ -848,12 +916,12 @@ export default { : ['useCallback(', ')']; return [ // TODO: also add an import? - fixer.insertTextBefore(construction.node.init, before), + fixer.insertTextBefore(construction.node.init!, before), // TODO: ideally we'd gather deps here but it would require // restructuring the rule code. This will cause a new lint // error to appear immediately for useCallback. Note we're // not adding [] because would that changes semantics. - fixer.insertTextAfter(construction.node.init, after), + fixer.insertTextAfter(construction.node.init!, after), ]; }, }, @@ -889,7 +957,7 @@ export default { } // Alphabetize the suggestions, but only if deps were already alphabetized. - function areDeclaredDepsAlphabetized() { + function areDeclaredDepsAlphabetized(): boolean { if (declaredDependencies.length === 0) { return true; } @@ -905,7 +973,7 @@ export default { // This function is the last step before printing a dependency, so now is a good time to // check whether any members in our path are always used as optional-only. In that case, // we will use ?. instead of . to concatenate those parts of the path. - function formatDependency(path) { + function formatDependency(path: string): string { const members = path.split('.'); let finalPath = ''; for (let i = 0; i < members.length; i++) { @@ -919,7 +987,12 @@ export default { return finalPath; } - function getWarningMessage(deps, singlePrefix, label, fixVerb) { + function getWarningMessage( + deps: Set, + singlePrefix: string, + label: string, + fixVerb: string, + ): string | null { if (deps.size === 0) { return null; } @@ -942,7 +1015,7 @@ export default { let extraWarning = ''; if (unnecessaryDependencies.size > 0) { - let badRef = null; + let badRef: string | null = null; Array.from(unnecessaryDependencies.keys()).forEach(key => { if (badRef !== null) { return; @@ -956,7 +1029,7 @@ export default { ` Mutable values like '${badRef}' aren't valid dependencies ` + "because mutating them doesn't re-render the component."; } else if (externalDependencies.size > 0) { - const dep = Array.from(externalDependencies)[0]; + const dep = Array.from(externalDependencies)[0]!; // Don't show this warning for things that likely just got moved *inside* the callback // because in that case they're clearly not referring to globals. if (!scope.set.has(dep)) { @@ -980,8 +1053,7 @@ export default { return; } let isPropsOnlyUsedInMembers = true; - for (let i = 0; i < refs.length; i++) { - const ref = refs[i]; + for (const ref of refs) { const id = fastFindReferenceWithParent( componentScope.block, ref.identifier, @@ -1008,14 +1080,14 @@ export default { ` However, 'props' will change when *any* prop changes, so the ` + `preferred fix is to destructure the 'props' object outside of ` + `the ${reactiveHookName} call and refer to those specific props ` + - `inside ${getSource(reactiveHook)}.`; + `inside ${getSourceCode().getText(reactiveHook)}.`; } } if (!extraWarning && missingDependencies.size > 0) { // See if the user is trying to avoid specifying a callable prop. // This usually means they're unaware of useCallback. - let missingCallbackDep = null; + let missingCallbackDep: string | null = null; missingDependencies.forEach(missingDep => { if (missingCallbackDep) { return; @@ -1023,19 +1095,22 @@ export default { // Is this a variable from top scope? const topScopeRef = componentScope.set.get(missingDep); const usedDep = dependencies.get(missingDep); - if (usedDep.references[0].resolved !== topScopeRef) { + if ( + !usedDep?.references || + usedDep?.references[0]?.resolved !== topScopeRef + ) { return; } // Is this a destructured prop? - const def = topScopeRef.defs[0]; + const def = topScopeRef?.defs[0]; if (def == null || def.name == null || def.type !== 'Parameter') { return; } // Was it called in at least one case? Then it's a function. let isFunctionCall = false; - let id; - for (let i = 0; i < usedDep.references.length; i++) { - id = usedDep.references[i].identifier; + let id: Identifier | undefined; + for (const reference of usedDep.references) { + id = reference.identifier; if ( id != null && id.parent != null && @@ -1064,17 +1139,21 @@ export default { } if (!extraWarning && missingDependencies.size > 0) { - let setStateRecommendation = null; - missingDependencies.forEach(missingDep => { + let setStateRecommendation: { + missingDep: string; + setter: string; + form: 'reducer' | 'updater' | 'inlineReducer'; + } | null = null; + for (const missingDep of missingDependencies) { if (setStateRecommendation !== null) { - return; + break; } - const usedDep = dependencies.get(missingDep); + const usedDep = dependencies.get(missingDep)!; const references = usedDep.references; let id; let maybeCall; - for (let i = 0; i < references.length; i++) { - id = references[i].identifier; + for (const reference of references) { + id = reference.identifier; maybeCall = id.parent; // Try to see if we have setState(someExpr(missingDep)). while (maybeCall != null && maybeCall !== componentScope.block) { @@ -1083,22 +1162,27 @@ export default { maybeCall.callee, ); if (correspondingStateVariable != null) { - if (correspondingStateVariable.name === missingDep) { + if ( + 'name' in correspondingStateVariable && + correspondingStateVariable.name === missingDep + ) { // setCount(count + 1) setStateRecommendation = { missingDep, - setter: maybeCall.callee.name, + setter: + 'name' in maybeCall.callee ? maybeCall.callee.name : '', form: 'updater', }; } else if (stateVariables.has(id)) { // setCount(count + increment) setStateRecommendation = { missingDep, - setter: maybeCall.callee.name, + setter: + 'name' in maybeCall.callee ? maybeCall.callee.name : '', form: 'reducer', }; } else { - const resolved = references[i].resolved; + const resolved = reference.resolved; if (resolved != null) { // If it's a parameter *and* a missing dep, // it must be a prop or something inside a prop. @@ -1107,7 +1191,10 @@ export default { if (def != null && def.type === 'Parameter') { setStateRecommendation = { missingDep, - setter: maybeCall.callee.name, + setter: + 'name' in maybeCall.callee + ? maybeCall.callee.name + : '', form: 'inlineReducer', }; } @@ -1122,7 +1209,7 @@ export default { break; } } - }); + } if (setStateRecommendation !== null) { switch (setStateRecommendation.form) { case 'reducer': @@ -1158,7 +1245,7 @@ export default { reportProblem({ node: declaredDependenciesNode, message: - `React Hook ${getSource(reactiveHook)} has ` + + `React Hook ${getSourceCode().getText(reactiveHook)} has ` + // To avoid a long message, show the next actionable item. (getWarningMessage(missingDependencies, 'a', 'missing', 'include') || getWarningMessage( @@ -1191,7 +1278,7 @@ export default { }); } - function visitCallExpression(node) { + function visitCallExpression(node: CallExpression): void { const callbackIndex = getReactiveHookCallbackIndex(node.callee, options); if (callbackIndex === -1) { // Not a React Hook call that needs deps. @@ -1199,7 +1286,9 @@ export default { } let callback = node.arguments[callbackIndex]; const reactiveHook = node.callee; - const reactiveHookName = getNodeWithoutReactNamespace(reactiveHook).name; + const nodeWithoutNamespace = getNodeWithoutReactNamespace(reactiveHook); + const reactiveHookName = + 'name' in nodeWithoutNamespace ? nodeWithoutNamespace.name : ''; const maybeNode = node.arguments[callbackIndex + 1]; const declaredDependenciesNode = maybeNode && @@ -1268,6 +1357,7 @@ export default { // The function passed as a callback is not written inline. // But perhaps it's in the dependencies array? if ( + 'elements' in declaredDependenciesNode && declaredDependenciesNode.elements && declaredDependenciesNode.elements.some( el => el && el.type === 'Identifier' && el.name === callback.name, @@ -1368,7 +1458,7 @@ export default { CallExpression: visitCallExpression, }; }, -}; +} satisfies Rule.RuleModule; // The meat of the logic. function collectRecommendations({ @@ -1377,6 +1467,12 @@ function collectRecommendations({ stableDependencies, externalDependencies, isEffect, +}: { + dependencies: Map; + declaredDependencies: DeclaredDependency[]; + stableDependencies: Set; + externalDependencies: Set; + isEffect: boolean; }) { // Our primary data structure. // It is a logical representation of property chains: @@ -1388,7 +1484,7 @@ function collectRecommendations({ // and the nodes that were *declared* as deps. Then we will // traverse it to learn which deps are missing or unnecessary. const depTree = createDepTree(); - function createDepTree() { + function createDepTree(): DependencyTreeNode { return { isUsed: false, // True if used in code isSatisfiedRecursively: false, // True if specified in deps @@ -1419,7 +1515,10 @@ function collectRecommendations({ }); // Tree manipulation helpers. - function getOrCreateNodeByPath(rootNode, path) { + function getOrCreateNodeByPath( + rootNode: DependencyTreeNode, + path: string, + ): DependencyTreeNode { const keys = path.split('.'); let node = rootNode; for (const key of keys) { @@ -1432,7 +1531,11 @@ function collectRecommendations({ } return node; } - function markAllParentsByPath(rootNode, path, fn) { + function markAllParentsByPath( + rootNode: DependencyTreeNode, + path: string, + fn: (node: DependencyTreeNode) => void, + ): void { const keys = path.split('.'); let node = rootNode; for (const key of keys) { @@ -1446,15 +1549,20 @@ function collectRecommendations({ } // Now we can learn which dependencies are missing or necessary. - const missingDependencies = new Set(); - const satisfyingDependencies = new Set(); + const missingDependencies = new Set(); + const satisfyingDependencies = new Set(); scanTreeRecursively( depTree, missingDependencies, satisfyingDependencies, key => key, ); - function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) { + function scanTreeRecursively( + node: DependencyTreeNode, + missingPaths: Set, + satisfyingPaths: Set, + keyToPath: (key: string) => string, + ): void { node.children.forEach((child, key) => { const path = keyToPath(key); if (child.isSatisfiedRecursively) { @@ -1484,9 +1592,9 @@ function collectRecommendations({ } // Collect suggestions in the order they were originally specified. - const suggestedDependencies = []; - const unnecessaryDependencies = new Set(); - const duplicateDependencies = new Set(); + const suggestedDependencies: string[] = []; + const unnecessaryDependencies = new Set(); + const duplicateDependencies = new Set(); declaredDependencies.forEach(({key}) => { // Does this declared dep satisfy a real need? if (satisfyingDependencies.has(key)) { @@ -1532,7 +1640,7 @@ function collectRecommendations({ // If the node will result in constructing a referentially unique value, return // its human readable type name, else return null. -function getConstructionExpressionType(node) { +function getConstructionExpressionType(node: Node): string | null { switch (node.type) { case 'ObjectExpression': return 'object'; @@ -1590,6 +1698,11 @@ function scanForConstructions({ declaredDependenciesNode, componentScope, scope, +}: { + declaredDependencies: DeclaredDependency[]; + declaredDependenciesNode: Node; + componentScope: Scope.Scope; + scope: Scope.Scope; }) { const constructions = declaredDependencies .map(({key}) => { @@ -1616,7 +1729,7 @@ function scanForConstructions({ const constantExpressionType = getConstructionExpressionType( node.node.init, ); - if (constantExpressionType != null) { + if (constantExpressionType) { return [ref, constantExpressionType]; } } @@ -1634,12 +1747,11 @@ function scanForConstructions({ } return null; }) - .filter(Boolean); + .filter(Boolean) as [Scope.Variable, string][]; - function isUsedOutsideOfHook(ref) { + function isUsedOutsideOfHook(ref: Scope.Variable): boolean { let foundWriteExpr = false; - for (let i = 0; i < ref.references.length; i++) { - const reference = ref.references[i]; + for (const reference of ref.references) { if (reference.writeExpr) { if (foundWriteExpr) { // Two writes to the same function. @@ -1650,7 +1762,7 @@ function scanForConstructions({ continue; } } - let currentScope = reference.from; + let currentScope: Scope.Scope | null = reference.from; while (currentScope !== scope && currentScope != null) { currentScope = currentScope.upper; } @@ -1666,7 +1778,7 @@ function scanForConstructions({ } return constructions.map(([ref, depType]) => ({ - construction: ref.defs[0], + construction: ref.defs[0] as Scope.Definition, depType, isUsedOutsideOfHook: isUsedOutsideOfHook(ref), })); @@ -1679,11 +1791,13 @@ function scanForConstructions({ * props.foo.(bar) => (props).foo.bar * props.foo.bar.(baz) => (props).foo.bar.baz */ -function getDependency(node) { +function getDependency(node: Node): Node { if ( + node.parent && (node.parent.type === 'MemberExpression' || node.parent.type === 'OptionalMemberExpression') && node.parent.object === node && + 'name' in node.parent.property && node.parent.property.name !== 'current' && !node.parent.computed && !( @@ -1713,9 +1827,13 @@ function getDependency(node) { * It just means there is an optional member somewhere inside. * This particular node might still represent a required member, so check .optional field. */ -function markNode(node, optionalChains, result) { +function markNode( + node: Node, + optionalChains: Map | null, + result: string, +): void { if (optionalChains) { - if (node.optional) { + if ('optional' in node && node.optional) { // We only want to consider it optional if *all* usages were optional. if (!optionalChains.has(result)) { // Mark as (maybe) optional. If there's a required usage, this will be overridden. @@ -1735,7 +1853,10 @@ function markNode(node, optionalChains, result) { * foo.bar(.)baz -> 'foo.bar.baz' * Otherwise throw. */ -function analyzePropertyChain(node, optionalChains) { +function analyzePropertyChain( + node: Node, + optionalChains: Map | null, +): string { if (node.type === 'Identifier' || node.type === 'JSXIdentifier') { const result = node.name; if (optionalChains) { @@ -1755,7 +1876,10 @@ function analyzePropertyChain(node, optionalChains) { const result = `${object}.${property}`; markNode(node, optionalChains, result); return result; - } else if (node.type === 'ChainExpression' && !node.computed) { + } else if ( + node.type === 'ChainExpression' && + (!('computed' in node) || !node.computed) + ) { const expression = node.expression; if (expression.type === 'CallExpression') { @@ -1772,7 +1896,9 @@ function analyzePropertyChain(node, optionalChains) { } } -function getNodeWithoutReactNamespace(node, options) { +function getNodeWithoutReactNamespace( + node: Expression | Super, +): Expression | Identifier | Super { if ( node.type === 'MemberExpression' && node.object.type === 'Identifier' && @@ -1790,7 +1916,13 @@ function getNodeWithoutReactNamespace(node, options) { // 0 for useEffect/useMemo/useCallback(fn). // 1 for useImperativeHandle(ref, fn). // For additionally configured Hooks, assume that they're like useEffect (0). -function getReactiveHookCallbackIndex(calleeNode, options) { +function getReactiveHookCallbackIndex( + calleeNode: Expression | Super, + options?: { + additionalHooks: RegExp | undefined; + enableDangerousAutofixThisMayCauseInfiniteLoops?: boolean; + }, +): 0 | -1 | 1 { const node = getNodeWithoutReactNamespace(calleeNode); if (node.type !== 'Identifier') { return -1; @@ -1812,8 +1944,11 @@ function getReactiveHookCallbackIndex(calleeNode, options) { let name; try { name = analyzePropertyChain(node, null); - } catch (error) { - if (/Unsupported node type/.test(error.message)) { + } catch (error: unknown) { + if ( + error instanceof Error && + /Unsupported node type/.test(error.message) + ) { return 0; } else { throw error; @@ -1836,12 +1971,12 @@ function getReactiveHookCallbackIndex(calleeNode, options) { * - optimized by only searching nodes with a range surrounding our target node * - agnostic to AST node types, it looks for `{ type: string, ... }` */ -function fastFindReferenceWithParent(start, target) { +function fastFindReferenceWithParent(start: Node, target: Node): Node | null { const queue = [start]; - let item = null; + let item: Node; while (queue.length) { - item = queue.shift(); + item = queue.shift() as Node; if (isSameIdentifier(item, target)) { return item; @@ -1872,7 +2007,7 @@ function fastFindReferenceWithParent(start, target) { return null; } -function joinEnglish(arr) { +function joinEnglish(arr: string[]): string { let s = ''; for (let i = 0; i < arr.length; i++) { s += arr[i]; @@ -1887,39 +2022,49 @@ function joinEnglish(arr) { return s; } -function isNodeLike(val) { +function isNodeLike(val: unknown): boolean { return ( typeof val === 'object' && val !== null && !Array.isArray(val) && + 'type' in val && typeof val.type === 'string' ); } -function isSameIdentifier(a, b) { +function isSameIdentifier(a: Node, b: Node): boolean { return ( (a.type === 'Identifier' || a.type === 'JSXIdentifier') && a.type === b.type && a.name === b.name && + !!a.range && + !!b.range && a.range[0] === b.range[0] && a.range[1] === b.range[1] ); } -function isAncestorNodeOf(a, b) { - return a.range[0] <= b.range[0] && a.range[1] >= b.range[1]; +function isAncestorNodeOf(a: Node, b: Node): boolean { + return ( + !!a.range && + !!b.range && + a.range[0] <= b.range[0] && + a.range[1] >= b.range[1] + ); } -function isUseEffectEventIdentifier(node) { +function isUseEffectEventIdentifier(node: Node): boolean { if (__EXPERIMENTAL__) { return node.type === 'Identifier' && node.name === 'useEffectEvent'; } return false; } -function getUnknownDependenciesMessage(reactiveHookName) { +function getUnknownDependenciesMessage(reactiveHookName: string): string { return ( `React Hook ${reactiveHookName} received a function whose dependencies ` + `are unknown. Pass an inline function instead.` ); } + +export default rule; diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts similarity index 85% rename from packages/eslint-plugin-react-hooks/src/RulesOfHooks.js rename to packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts index fc340519e233e..24870d71614ea 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts @@ -4,18 +4,16 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - -/* global BigInt */ /* eslint-disable no-for-of-loops/no-for-of-loops */ -'use strict'; +import type {Rule, Scope} from 'eslint'; +import type {CallExpression, DoWhileStatement, Node} from 'estree'; /** * Catch all identifiers that begin with "use" followed by an uppercase Latin * character to exclude identifiers like "user". */ - -function isHookName(s) { +function isHookName(s: string): boolean { return s === 'use' || /^use[A-Z0-9]/.test(s); } @@ -23,8 +21,7 @@ function isHookName(s) { * We consider hooks to be a hook name identifier or a member expression * containing a hook name. */ - -function isHook(node) { +function isHook(node: Node): boolean { if (node.type === 'Identifier') { return isHookName(node.name); } else if ( @@ -44,16 +41,17 @@ function isHook(node) { * Checks if the node is a React component name. React component names must * always start with an uppercase letter. */ - -function isComponentName(node) { +function isComponentName(node: Node): boolean { return node.type === 'Identifier' && /^[A-Z]/.test(node.name); } -function isReactFunction(node, functionName) { +function isReactFunction(node: Node, functionName: string): boolean { return ( - node.name === functionName || + ('name' in node && node.name === functionName) || (node.type === 'MemberExpression' && + 'name' in node.object && node.object.name === 'React' && + 'name' in node.property && node.property.name === functionName) ); } @@ -62,10 +60,10 @@ function isReactFunction(node, functionName) { * Checks if the node is a callback argument of forwardRef. This render function * should follow the rules of hooks. */ - -function isForwardRefCallback(node) { +function isForwardRefCallback(node: Node): boolean { return !!( node.parent && + 'callee' in node.parent && node.parent.callee && isReactFunction(node.parent.callee, 'forwardRef') ); @@ -75,16 +73,16 @@ function isForwardRefCallback(node) { * Checks if the node is a callback argument of React.memo. This anonymous * functional component should follow the rules of hooks. */ - -function isMemoCallback(node) { +function isMemoCallback(node: Node): boolean { return !!( node.parent && + 'callee' in node.parent && node.parent.callee && isReactFunction(node.parent.callee, 'memo') ); } -function isInsideComponentOrHook(node) { +function isInsideComponentOrHook(node: Node | undefined): boolean { while (node) { const functionName = getFunctionName(node); if (functionName) { @@ -100,7 +98,7 @@ function isInsideComponentOrHook(node) { return false; } -function isInsideDoWhileLoop(node) { +function isInsideDoWhileLoop(node: Node | undefined): node is DoWhileStatement { while (node) { if (node.type === 'DoWhileStatement') { return true; @@ -110,18 +108,18 @@ function isInsideDoWhileLoop(node) { return false; } -function isUseEffectEventIdentifier(node) { +function isUseEffectEventIdentifier(node: Node): boolean { if (__EXPERIMENTAL__) { return node.type === 'Identifier' && node.name === 'useEffectEvent'; } return false; } -function isUseIdentifier(node) { +function isUseIdentifier(node: Node): boolean { return isReactFunction(node, 'use'); } -export default { +const rule = { meta: { type: 'problem', docs: { @@ -130,25 +128,28 @@ export default { url: 'https://reactjs.org/docs/hooks-rules.html', }, }, - create(context) { - let lastEffect = null; - const codePathReactHooksMapStack = []; - const codePathSegmentStack = []; + create(context: Rule.RuleContext) { + let lastEffect: CallExpression | null = null; + const codePathReactHooksMapStack: Map[] = []; + const codePathSegmentStack: Rule.CodePathSegment[] = []; const useEffectEventFunctions = new WeakSet(); // For a given scope, iterate through the references and add all useEffectEvent definitions. We can // do this in non-Program nodes because we can rely on the assumption that useEffectEvent functions // can only be declared within a component or hook at its top level. - function recordAllUseEffectEventFunctions(scope) { + function recordAllUseEffectEventFunctions(scope: Scope.Scope): void { for (const reference of scope.references) { const parent = reference.identifier.parent; if ( - parent.type === 'VariableDeclarator' && + parent?.type === 'VariableDeclarator' && parent.init && parent.init.type === 'CallExpression' && parent.init.callee && isUseEffectEventIdentifier(parent.init.callee) ) { + if (reference.resolved === null) { + throw new Error('Unexpected null reference.resolved'); + } for (const ref of reference.resolved.references) { if (ref !== reference) { useEffectEventFunctions.add(ref.identifier); @@ -159,26 +160,26 @@ export default { } /** - * SourceCode#getText that also works down to ESLint 3.0.0 + * SourceCode that also works down to ESLint 3.0.0 */ - const getSource = - typeof context.getSource === 'function' - ? node => { - return context.getSource(node); + const getSourceCode = + typeof context.getSourceCode === 'function' + ? () => { + return context.getSourceCode(); } - : node => { - return context.sourceCode.getText(node); + : () => { + return context.sourceCode; }; /** * SourceCode#getScope that also works down to ESLint 3.0.0 */ const getScope = typeof context.getScope === 'function' - ? () => { + ? (): Scope.Scope => { return context.getScope(); } - : node => { - return context.sourceCode.getScope(node); + : (node: Node): Scope.Scope => { + return getSourceCode().getScope(node); }; return { @@ -187,7 +188,10 @@ export default { onCodePathSegmentEnd: () => codePathSegmentStack.pop(), // Maintain code path stack as we traverse. - onCodePathStart: () => codePathReactHooksMapStack.push(new Map()), + onCodePathStart: () => + codePathReactHooksMapStack.push( + new Map(), + ), // Process our code path. // @@ -195,8 +199,10 @@ export default { // segment and reachable from every final segment. onCodePathEnd(codePath, codePathNode) { const reactHooksMap = codePathReactHooksMapStack.pop(); - if (reactHooksMap.size === 0) { + if (reactHooksMap?.size === 0) { return; + } else if (typeof reactHooksMap === 'undefined') { + throw new Error('Unexpected undefined reactHooksMap'); } // All of the segments which are cyclic are recorded in this set. @@ -223,11 +229,13 @@ export default { * * Populates `cyclic` with cyclic segments. */ - - function countPathsFromStart(segment, pathHistory) { + function countPathsFromStart( + segment: Rule.CodePathSegment, + pathHistory?: Set, + ): bigint { const {cache} = countPathsFromStart; let paths = cache.get(segment.id); - const pathList = new Set(pathHistory); + const pathList = new Set(pathHistory); // If `pathList` includes the current segment then we've found a cycle! // We need to fill `cyclic` with all segments inside cycle @@ -295,7 +303,10 @@ export default { * Populates `cyclic` with cyclic segments. */ - function countPathsToEnd(segment, pathHistory) { + function countPathsToEnd( + segment: Rule.CodePathSegment, + pathHistory?: Set, + ): bigint { const {cache} = countPathsToEnd; let paths = cache.get(segment.id); const pathList = new Set(pathHistory); @@ -359,7 +370,9 @@ export default { * so we would return that. */ - function shortestPathLengthToStart(segment) { + function shortestPathLengthToStart( + segment: Rule.CodePathSegment, + ): number { const {cache} = shortestPathLengthToStart; let length = cache.get(segment.id); @@ -392,9 +405,9 @@ export default { return length; } - countPathsFromStart.cache = new Map(); - countPathsToEnd.cache = new Map(); - shortestPathLengthToStart.cache = new Map(); + countPathsFromStart.cache = new Map(); + countPathsToEnd.cache = new Map(); + shortestPathLengthToStart.cache = new Map(); // Count all code paths to the end of our component/hook. Also primes // the `countPathsToEnd` cache. @@ -502,7 +515,7 @@ export default { context.report({ node: hook, message: - `React Hook "${getSource(hook)}" may be executed ` + + `React Hook "${getSourceCode().getText(hook)}" may be executed ` + 'more than once. Possibly because it is called in a loop. ' + 'React Hooks must be called in the exact same order in ' + 'every component render.', @@ -516,12 +529,13 @@ export default { // called in. if (isDirectlyInsideComponentOrHook) { // Report an error if the hook is called inside an async function. + // @ts-expect-error the above check hasn't properly type-narrowed `codePathNode` (async doesn't exist on Node) const isAsyncFunction = codePathNode.async; if (isAsyncFunction) { context.report({ node: hook, message: - `React Hook "${getSource(hook)}" cannot be ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be ` + 'called in an async function.', }); } @@ -537,7 +551,7 @@ export default { !isInsideDoWhileLoop(hook) // wrapping do/while loops are checked separately. ) { const message = - `React Hook "${getSource(hook)}" is called ` + + `React Hook "${getSourceCode().getText(hook)}" is called ` + 'conditionally. React Hooks must be called in the exact ' + 'same order in every component render.' + (possiblyHasEarlyReturn @@ -549,21 +563,22 @@ export default { } else if ( codePathNode.parent && (codePathNode.parent.type === 'MethodDefinition' || + // @ts-expect-error `ClassProperty` was removed from typescript-estree in https://github.com/typescript-eslint/typescript-eslint/pull/3806 codePathNode.parent.type === 'ClassProperty' || codePathNode.parent.type === 'PropertyDefinition') && codePathNode.parent.value === codePathNode ) { // Custom message for hooks inside a class const message = - `React Hook "${getSource(hook)}" cannot be called ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be called ` + 'in a class component. React Hooks must be called in a ' + 'React function component or a custom React Hook function.'; context.report({node: hook, message}); } else if (codePathFunctionName) { // Custom message if we found an invalid function name. const message = - `React Hook "${getSource(hook)}" is called in ` + - `function "${getSource(codePathFunctionName)}" ` + + `React Hook "${getSourceCode().getText(hook)}" is called in ` + + `function "${getSourceCode().getText(codePathFunctionName)}" ` + 'that is neither a React function component nor a custom ' + 'React Hook function.' + ' React component names must start with an uppercase letter.' + @@ -572,7 +587,7 @@ export default { } else if (codePathNode.type === 'Program') { // These are dangerous if you have inline requires enabled. const message = - `React Hook "${getSource(hook)}" cannot be called ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be called ` + 'at the top level. React Hooks must be called in a ' + 'React function component or a custom React Hook function.'; context.report({node: hook, message}); @@ -585,7 +600,7 @@ export default { // `use(...)` can be called in callbacks. if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) { const message = - `React Hook "${getSource(hook)}" cannot be called ` + + `React Hook "${getSourceCode().getText(hook)}" cannot be called ` + 'inside a callback. React Hooks must be called in a ' + 'React function component or a custom React Hook function.'; context.report({node: hook, message}); @@ -638,7 +653,7 @@ export default { context.report({ node, message: - `\`${getSource( + `\`${getSourceCode().getText( node, )}\` is a function created with React Hook "useEffectEvent", and can only be called from ` + 'the same component. They cannot be assigned to variables or passed down.', @@ -667,7 +682,7 @@ export default { }, }; }, -}; +} satisfies Rule.RuleModule; /** * Gets the static name of a function AST node. For function declarations it is @@ -677,7 +692,7 @@ export default { * same AST nodes with some exceptions to better fit our use case. */ -function getFunctionName(node) { +function getFunctionName(node: Node) { if ( node.type === 'FunctionDeclaration' || (node.type === 'FunctionExpression' && node.id) @@ -693,20 +708,20 @@ function getFunctionName(node) { node.type === 'ArrowFunctionExpression' ) { if ( - node.parent.type === 'VariableDeclarator' && + node.parent?.type === 'VariableDeclarator' && node.parent.init === node ) { // const useHook = () => {}; return node.parent.id; } else if ( - node.parent.type === 'AssignmentExpression' && + node.parent?.type === 'AssignmentExpression' && node.parent.right === node && node.parent.operator === '=' ) { // useHook = () => {}; return node.parent.left; } else if ( - node.parent.type === 'Property' && + node.parent?.type === 'Property' && node.parent.value === node && !node.parent.computed ) { @@ -721,8 +736,9 @@ function getFunctionName(node) { // class {useHook = () => {}} // class {useHook() {}} } else if ( - node.parent.type === 'AssignmentPattern' && + node.parent?.type === 'AssignmentPattern' && node.parent.right === node && + // @ts-expect-error Property computed does not exist on type `AssignmentPattern`. !node.parent.computed ) { // const {useHook = () => {}} = {}; @@ -742,7 +758,8 @@ function getFunctionName(node) { /** * Convenience function for peeking the last item in a stack. */ - -function last(array) { - return array[array.length - 1]; +function last(array: T[]): T { + return array[array.length - 1] as T; } + +export default rule; diff --git a/packages/eslint-plugin-react-hooks/src/index.js b/packages/eslint-plugin-react-hooks/src/index.js deleted file mode 100644 index 0ebfbc91a5a8d..0000000000000 --- a/packages/eslint-plugin-react-hooks/src/index.js +++ /dev/null @@ -1,61 +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. - */ - -'use strict'; - -import RulesOfHooks from './RulesOfHooks'; -import ExhaustiveDeps from './ExhaustiveDeps'; - -// All rules -export const rules = { - 'rules-of-hooks': RulesOfHooks, - 'exhaustive-deps': ExhaustiveDeps, -}; - -// Config rules -const configRules = { - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', -}; - -// Legacy config -const legacyRecommendedConfig = { - plugins: ['react-hooks'], - rules: configRules, -}; - -// Base plugin object -const reactHooksPlugin = { - meta: {name: 'eslint-plugin-react-hooks'}, - rules, -}; - -// Flat config -const flatRecommendedConfig = { - name: 'react-hooks/recommended', - plugins: {'react-hooks': reactHooksPlugin}, - rules: configRules, -}; - -export const configs = { - /** Legacy recommended config, to be used with rc-based configurations */ - 'recommended-legacy': legacyRecommendedConfig, - - /** Latest recommended config, to be used with flat configurations */ - 'recommended-latest': flatRecommendedConfig, - - /** - * 'recommended' is currently aliased to the legacy / rc recommended config) to maintain backwards compatibility. - * This is deprecated and in v6, it will switch to alias the flat recommended config. - */ - recommended: legacyRecommendedConfig, -}; - -export default { - ...reactHooksPlugin, - configs, -}; diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts new file mode 100644 index 0000000000000..42b55320fcd89 --- /dev/null +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -0,0 +1,64 @@ +/** + * 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 RulesOfHooks from './RulesOfHooks'; +import ExhaustiveDeps from './ExhaustiveDeps'; +import type {ESLint, Linter, Rule} from 'eslint'; + +// All rules +const rules = { + 'rules-of-hooks': RulesOfHooks, + 'exhaustive-deps': ExhaustiveDeps, +} satisfies Record; + +// Config rules +const configRules = { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', +} satisfies Linter.RulesRecord; + +// Legacy config +const legacyRecommendedConfig = { + plugins: ['react-hooks'], + rules: configRules, +} satisfies Linter.LegacyConfig; + +// Plugin object +const plugin = { + // TODO: Make this more dynamic to populate version from package.json. + // This can be done by injecting at build time, since importing the package.json isn't an option in Meta + meta: {name: 'eslint-plugin-react-hooks'}, + rules, + configs: { + /** Legacy recommended config, to be used with rc-based configurations */ + 'recommended-legacy': legacyRecommendedConfig, + + /** + * 'recommended' is currently aliased to the legacy / rc recommended config) to maintain backwards compatibility. + * This is deprecated and in v6, it will switch to alias the flat recommended config. + */ + recommended: legacyRecommendedConfig, + + /** Latest recommended config, to be used with flat configurations */ + 'recommended-latest': { + name: 'react-hooks/recommended', + plugins: { + get 'react-hooks'(): ESLint.Plugin { + return plugin; + }, + }, + rules: configRules, + }, + }, +} satisfies ESLint.Plugin; + +const configs = plugin.configs; +const meta = plugin.meta; +export {configs, meta, rules}; + +// TODO: If the plugin is ever updated to be pure ESM and drops support for rc-based configs, then it should be exporting the plugin as default +// instead of individual named exports. +// export default plugin; diff --git a/packages/eslint-plugin-react-hooks/src/types/estree.d.ts b/packages/eslint-plugin-react-hooks/src/types/estree.d.ts index 10337ef7d6bf0..f266369704885 100644 --- a/packages/eslint-plugin-react-hooks/src/types/estree.d.ts +++ b/packages/eslint-plugin-react-hooks/src/types/estree.d.ts @@ -1,3 +1,5 @@ +import {Expression, Identifier, Node} from 'estree-jsx'; + /** * This file augments the `estree` types to include types that are not built-in to `estree` or `estree-jsx`. * This is necessary because the `estree` types are used by ESLint, and ESLint does not natively support diff --git a/packages/eslint-plugin-react-hooks/tsconfig.json b/packages/eslint-plugin-react-hooks/tsconfig.json index db8a4893649ea..e67b840d3fd7a 100644 --- a/packages/eslint-plugin-react-hooks/tsconfig.json +++ b/packages/eslint-plugin-react-hooks/tsconfig.json @@ -6,7 +6,6 @@ "moduleResolution": "Bundler", "lib": ["ES2020"], "rootDir": ".", - "noEmit": true, "sourceMap": false, "types": ["estree-jsx", "node"] }, diff --git a/packages/eslint-plugin-react-hooks/tsup.config.ts b/packages/eslint-plugin-react-hooks/tsup.config.ts deleted file mode 100644 index e275d0495e26c..0000000000000 --- a/packages/eslint-plugin-react-hooks/tsup.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {defineConfig} from 'tsup'; - -export default defineConfig({ - clean: true, - dts: true, - entry: ['src/index.ts'], - format: ['cjs'], - outDir: 'build', -}); diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index c66cff82c5ac2..0a378c4f31bf7 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -1184,17 +1184,19 @@ const bundles = [ /******* ESLint Plugin for Hooks *******/ { - // TODO: it's awkward to create a bundle for this but if we don't, the package - // won't get copied. We also can't create just DEV bundle because it contains a - // NODE_ENV check inside. We should probably tweak our build process to allow - // "raw" packages that don't get bundled. - bundleTypes: [NODE_DEV, NODE_PROD], + // TODO: we're building this from typescript source now, but there's really + // no reason to have both dev and prod for this package. It's + // currently required in order for the package to be copied over correctly. + // So, it would be worth improving that flow. + name: 'eslint-plugin-react-hooks', + bundleTypes: [NODE_DEV, NODE_PROD, CJS_DTS], moduleType: ISOMORPHIC, - entry: 'eslint-plugin-react-hooks', + entry: 'eslint-plugin-react-hooks/src/index.ts', global: 'ESLintPluginReactHooks', minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: false, externals: [], + tsconfig: './packages/eslint-plugin-react-hooks/tsconfig.json', }, /******* React Fresh *******/ diff --git a/yarn.lock b/yarn.lock index 44d590625111c..ba71ba7abe323 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2403,131 +2403,6 @@ opn "5.3.0" react "^16.13.1" -"@esbuild/aix-ppc64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461" - integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA== - -"@esbuild/android-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894" - integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg== - -"@esbuild/android-arm@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3" - integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q== - -"@esbuild/android-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb" - integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw== - -"@esbuild/darwin-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936" - integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA== - -"@esbuild/darwin-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9" - integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA== - -"@esbuild/freebsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00" - integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg== - -"@esbuild/freebsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f" - integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q== - -"@esbuild/linux-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43" - integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg== - -"@esbuild/linux-arm@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736" - integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA== - -"@esbuild/linux-ia32@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5" - integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw== - -"@esbuild/linux-loong64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc" - integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ== - -"@esbuild/linux-mips64el@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb" - integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw== - -"@esbuild/linux-ppc64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412" - integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw== - -"@esbuild/linux-riscv64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694" - integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q== - -"@esbuild/linux-s390x@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577" - integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw== - -"@esbuild/linux-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f" - integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q== - -"@esbuild/netbsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6" - integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw== - -"@esbuild/netbsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40" - integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw== - -"@esbuild/openbsd-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f" - integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A== - -"@esbuild/openbsd-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205" - integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA== - -"@esbuild/sunos-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6" - integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig== - -"@esbuild/win32-arm64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85" - integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ== - -"@esbuild/win32-ia32@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2" - integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA== - -"@esbuild/win32-x64@0.24.2": - version "0.24.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b" - integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg== - "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -2924,15 +2799,6 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" - integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -3396,101 +3262,6 @@ estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz#e554185b1afa5509a7a4040d15ec0c3b4435ded1" - integrity sha512-l6CtzHYo8D2TQ3J7qJNpp3Q1Iye56ssIAtqbM2H8axxCEEwvN7o8Ze9PuIapbxFL3OHrJU2JBX6FIIVnP/rYyw== - -"@rollup/rollup-android-arm64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.7.tgz#b1ee64bb413b2feba39803b0a1bebf2a9f3d70e1" - integrity sha512-KvyJpFUueUnSp53zhAa293QBYqwm94TgYTIfXyOTtidhm5V0LbLCJQRGkQClYiX3FXDQGSvPxOTD/6rPStMMDg== - -"@rollup/rollup-darwin-arm64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.7.tgz#bfdce3e07a345dd1bd628f3b796050f39629d7f0" - integrity sha512-jq87CjmgL9YIKvs8ybtIC98s/M3HdbqXhllcy9EdLV0yMg1DpxES2gr65nNy7ObNo/vZ/MrOTxt0bE5LinL6mA== - -"@rollup/rollup-darwin-x64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.7.tgz#781a94a537c57bdf0a500e47a25ab5985e5e8dff" - integrity sha512-rSI/m8OxBjsdnMMg0WEetu/w+LhLAcCDEiL66lmMX4R3oaml3eXz3Dxfvrxs1FbzPbJMaItQiksyMfv1hoIxnA== - -"@rollup/rollup-freebsd-arm64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.7.tgz#7a028357cbd12c5869c446ad18177c89f3405102" - integrity sha512-oIoJRy3ZrdsXpFuWDtzsOOa/E/RbRWXVokpVrNnkS7npz8GEG++E1gYbzhYxhxHbO2om1T26BZjVmdIoyN2WtA== - -"@rollup/rollup-freebsd-x64@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.7.tgz#f24836a6371cccc4408db74f0fd986dacf098950" - integrity sha512-X++QSLm4NZfZ3VXGVwyHdRf58IBbCu9ammgJxuWZYLX0du6kZvdNqPwrjvDfwmi6wFdvfZ/s6K7ia0E5kI7m8Q== - -"@rollup/rollup-linux-arm-gnueabihf@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.7.tgz#95f27e96f0eb9b9ae9887739a8b6dffc90c1237f" - integrity sha512-Z0TzhrsNqukTz3ISzrvyshQpFnFRfLunYiXxlCRvcrb3nvC5rVKI+ZXPFG/Aa4jhQa1gHgH3A0exHaRRN4VmdQ== - -"@rollup/rollup-linux-arm-musleabihf@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.7.tgz#677b34fba9d070877736c3fe8b02aacb5e142d97" - integrity sha512-nkznpyXekFAbvFBKBy4nNppSgneB1wwG1yx/hujN3wRnhnkrYVugMTCBXED4+Ni6thoWfQuHNYbFjgGH0MBXtw== - -"@rollup/rollup-linux-arm64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.7.tgz#32d3d19dedde54e91574a098f22ea43a09cf63dd" - integrity sha512-KCjlUkcKs6PjOcxolqrXglBDcfCuUCTVlX5BgzgoJHw+1rWH1MCkETLkLe5iLLS9dP5gKC7mp3y6x8c1oGBUtA== - -"@rollup/rollup-linux-arm64-musl@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.7.tgz#a58dff44a18696df65ed8c0ad68a2945cf900484" - integrity sha512-uFLJFz6+utmpbR313TTx+NpPuAXbPz4BhTQzgaP0tozlLnGnQ6rCo6tLwaSa6b7l6gRErjLicXQ1iPiXzYotjw== - -"@rollup/rollup-linux-loongarch64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.7.tgz#a7488ab078233111e8aeb370d1ecf107ec7e1716" - integrity sha512-ws8pc68UcJJqCpneDFepnwlsMUFoWvPbWXT/XUrJ7rWUL9vLoIN3GAasgG+nCvq8xrE3pIrd+qLX/jotcLy0Qw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.7.tgz#e9b9c0d6bd248a92b2d6ec01ebf99c62ae1f2e9a" - integrity sha512-vrDk9JDa/BFkxcS2PbWpr0C/LiiSLxFbNOBgfbW6P8TBe9PPHx9Wqbvx2xgNi1TOAyQHQJ7RZFqBiEohm79r0w== - -"@rollup/rollup-linux-riscv64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.7.tgz#0df84ce2bea48ee686fb55060d76ab47aff45c4c" - integrity sha512-rB+ejFyjtmSo+g/a4eovDD1lHWHVqizN8P0Hm0RElkINpS0XOdpaXloqM4FBkF9ZWEzg6bezymbpLmeMldfLTw== - -"@rollup/rollup-linux-s390x-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.7.tgz#73df374c57d036856e33dbd2715138922e91e452" - integrity sha512-nNXNjo4As6dNqRn7OrsnHzwTgtypfRA3u3AKr0B3sOOo+HkedIbn8ZtFnB+4XyKJojIfqDKmbIzO1QydQ8c+Pw== - -"@rollup/rollup-linux-x64-gnu@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.7.tgz#f27af0b55f0cdd84e182e6cd44a6d03da0458149" - integrity sha512-9kPVf9ahnpOMSGlCxXGv980wXD0zRR3wyk8+33/MXQIpQEOpaNe7dEHm5LMfyRZRNt9lMEQuH0jUKj15MkM7QA== - -"@rollup/rollup-linux-x64-musl@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.7.tgz#c7981ad5cfb8c3cd5d643d33ca54e4d2802b9201" - integrity sha512-7wJPXRWTTPtTFDFezA8sle/1sdgxDjuMoRXEKtx97ViRxGGkVQYovem+Q8Pr/2HxiHp74SSRG+o6R0Yq0shPwQ== - -"@rollup/rollup-win32-arm64-msvc@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.7.tgz#06cedc0ef3cbf1cbd8abcf587090712e40ae6941" - integrity sha512-MN7aaBC7mAjsiMEZcsJvwNsQVNZShgES/9SzWp1HC9Yjqb5OpexYnRjF7RmE4itbeesHMYYQiAtUAQaSKs2Rfw== - -"@rollup/rollup-win32-ia32-msvc@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.7.tgz#90b39b977b14961a769be6ea61238e7fc668dd4d" - integrity sha512-aeawEKYswsFu1LhDM9RIgToobquzdtSc4jSVqHV8uApz4FVvhFl/mKh92wc8WpFc6aYCothV/03UjY6y7yLgbg== - -"@rollup/rollup-win32-x64-msvc@4.34.7": - version "4.34.7" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.7.tgz#6531d61e7141091eaab0461ee8e0380c10e4ca57" - integrity sha512-4ZedScpxxIrVO7otcZ8kCX1mZArtH2Wfj3uFCxRJ9NO80gg1XV0U/b2f/MKaGwj2X3QopHfoWiDQ917FRpwY3w== - "@sinclair/typebox@^0.25.16": version "0.25.24" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" @@ -3680,7 +3451,7 @@ dependencies: "@types/estree" "*" -"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.6": +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== @@ -5760,13 +5531,6 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" -bundle-require@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee" - integrity sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== - dependencies: - load-tsconfig "^0.2.3" - bunyan@1.8.15: version "1.8.15" resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46" @@ -5787,11 +5551,6 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cac@^6.7.14: - version "6.7.14" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" - integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== - cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -6093,13 +5852,6 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" -chokidar@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - chownr@^1.0.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -6368,7 +6120,7 @@ commander@^2.18.0, commander@^2.20.0, commander@^2.6.0, commander@^2.8.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.0, commander@^4.0.1: +commander@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -6511,11 +6263,6 @@ connect-history-api-fallback@^2.0.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== -consola@^3.2.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/consola/-/consola-3.4.0.tgz#4cfc9348fd85ed16a17940b3032765e31061ab88" - integrity sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA== - console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -7039,13 +6786,6 @@ debug@^4.3.4, debug@~4.3.1: dependencies: ms "2.1.2" -debug@^4.3.7: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - decamelize@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-6.0.0.tgz#8cad4d916fde5c41a264a43d0ecc56fe3d31749e" @@ -7744,37 +7484,6 @@ es6-error@4.1.1, es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild@^0.24.0: - version "0.24.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d" - integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA== - optionalDependencies: - "@esbuild/aix-ppc64" "0.24.2" - "@esbuild/android-arm" "0.24.2" - "@esbuild/android-arm64" "0.24.2" - "@esbuild/android-x64" "0.24.2" - "@esbuild/darwin-arm64" "0.24.2" - "@esbuild/darwin-x64" "0.24.2" - "@esbuild/freebsd-arm64" "0.24.2" - "@esbuild/freebsd-x64" "0.24.2" - "@esbuild/linux-arm" "0.24.2" - "@esbuild/linux-arm64" "0.24.2" - "@esbuild/linux-ia32" "0.24.2" - "@esbuild/linux-loong64" "0.24.2" - "@esbuild/linux-mips64el" "0.24.2" - "@esbuild/linux-ppc64" "0.24.2" - "@esbuild/linux-riscv64" "0.24.2" - "@esbuild/linux-s390x" "0.24.2" - "@esbuild/linux-x64" "0.24.2" - "@esbuild/netbsd-arm64" "0.24.2" - "@esbuild/netbsd-x64" "0.24.2" - "@esbuild/openbsd-arm64" "0.24.2" - "@esbuild/openbsd-x64" "0.24.2" - "@esbuild/sunos-x64" "0.24.2" - "@esbuild/win32-arm64" "0.24.2" - "@esbuild/win32-ia32" "0.24.2" - "@esbuild/win32-x64" "0.24.2" - escalade@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -8649,11 +8358,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fdir@^6.4.2: - version "6.4.3" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72" - integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw== - fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -9292,18 +8996,6 @@ glob@^10.2.5: minipass "^5.0.0 || ^6.0.2" path-scurry "^1.7.0" -glob@^10.3.10: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - glob@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -11489,11 +11181,6 @@ jose@5.4.1: resolved "https://registry.yarnpkg.com/jose/-/jose-5.4.1.tgz#b471ee3963920ba5452fd1b1398c8ba72a7b2fcf" integrity sha512-U6QajmpV/nhL9SyfAewo000fkiRQ+Yd2H0lBxJJ9apjpOgkOcBQJWOrMo917lxLptdS/n/o/xPzMkXhF46K8hQ== -joycon@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" - integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== - jpeg-js@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" @@ -11880,11 +11567,6 @@ lighthouse-logger@^1.0.0: debug "^2.6.8" marky "^1.2.0" -lilconfig@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" - integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -11906,11 +11588,6 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -load-tsconfig@^0.2.3: - version "0.2.5" - resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.5.tgz#453b8cd8961bfb912dea77eb6c168fe8cca3d3a1" - integrity sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== - loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -12047,11 +11724,6 @@ lodash.omitby@4.6.0: resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" integrity sha1-XBX/R1StVVAWtTwEExHo8HkgR5E= -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== - lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -12572,7 +12244,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1, ms@^2.1.2, ms@^2.1.3: +ms@2.1.3, ms@^2.1.1, ms@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -12604,7 +12276,7 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" -mz@2.7.0, mz@^2.7.0: +mz@2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== @@ -13646,11 +13318,6 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -13821,13 +13488,6 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -postcss-load-config@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" - integrity sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== - dependencies: - lilconfig "^3.1.1" - postcss-modules-extract-imports@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a" @@ -14556,11 +14216,6 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" - integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -15003,34 +14658,6 @@ rollup@^3.29.5: optionalDependencies: fsevents "~2.3.2" -rollup@^4.24.0: - version "4.34.7" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.7.tgz#e00d8550688a616a3481c6446bb688d4c753ba8f" - integrity sha512-8qhyN0oZ4x0H6wmBgfKxJtxM7qS98YJ0k0kNh5ECVtuchIJ7z9IVVvzpmtQyT10PXKMtBxYr1wQ5Apg8RS8kXQ== - dependencies: - "@types/estree" "1.0.6" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.34.7" - "@rollup/rollup-android-arm64" "4.34.7" - "@rollup/rollup-darwin-arm64" "4.34.7" - "@rollup/rollup-darwin-x64" "4.34.7" - "@rollup/rollup-freebsd-arm64" "4.34.7" - "@rollup/rollup-freebsd-x64" "4.34.7" - "@rollup/rollup-linux-arm-gnueabihf" "4.34.7" - "@rollup/rollup-linux-arm-musleabihf" "4.34.7" - "@rollup/rollup-linux-arm64-gnu" "4.34.7" - "@rollup/rollup-linux-arm64-musl" "4.34.7" - "@rollup/rollup-linux-loongarch64-gnu" "4.34.7" - "@rollup/rollup-linux-powerpc64le-gnu" "4.34.7" - "@rollup/rollup-linux-riscv64-gnu" "4.34.7" - "@rollup/rollup-linux-s390x-gnu" "4.34.7" - "@rollup/rollup-linux-x64-gnu" "4.34.7" - "@rollup/rollup-linux-x64-musl" "4.34.7" - "@rollup/rollup-win32-arm64-msvc" "4.34.7" - "@rollup/rollup-win32-ia32-msvc" "4.34.7" - "@rollup/rollup-win32-x64-msvc" "4.34.7" - fsevents "~2.3.2" - rrweb-cssom@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" @@ -15671,13 +15298,6 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@0.8.0-beta.0: - version "0.8.0-beta.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" - integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== - dependencies: - whatwg-url "^7.0.0" - source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -16136,19 +15756,6 @@ style-loader@^1.2.1: loader-utils "^2.0.0" schema-utils "^2.6.6" -sucrase@^3.35.0: - version "3.35.0" - resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" - integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== - dependencies: - "@jridgewell/gen-mapping" "^0.3.2" - commander "^4.0.0" - glob "^10.3.10" - lines-and-columns "^1.1.6" - mz "^2.7.0" - pirates "^4.0.1" - ts-interface-checker "^0.1.9" - sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -16437,19 +16044,6 @@ tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinyexec@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" - integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== - -tinyglobby@^0.2.9: - version "0.2.10" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f" - integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew== - dependencies: - fdir "^6.4.2" - picomatch "^4.0.2" - titleize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" @@ -16556,13 +16150,6 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== - dependencies: - punycode "^2.1.0" - tr46@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" @@ -16580,11 +16167,6 @@ traverse-chain@~0.1.0: resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1" integrity sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE= -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -16602,11 +16184,6 @@ ts-api-utils@^1.0.1: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - ts-node@8.9.1: version "8.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5" @@ -16633,28 +16210,6 @@ tslib@^2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== -tsup@^8.3.5: - version "8.3.6" - resolved "https://registry.yarnpkg.com/tsup/-/tsup-8.3.6.tgz#a10eb2dc27f84b510a0f00341ab75cad03d13a88" - integrity sha512-XkVtlDV/58S9Ye0JxUUTcrQk4S+EqlOHKzg6Roa62rdjL1nGWNUstG0xgI4vanHdfIpjP448J8vlN0oK6XOJ5g== - dependencies: - bundle-require "^5.0.0" - cac "^6.7.14" - chokidar "^4.0.1" - consola "^3.2.3" - debug "^4.3.7" - esbuild "^0.24.0" - joycon "^3.1.1" - picocolors "^1.1.1" - postcss-load-config "^6.0.1" - resolve-from "^5.0.0" - rollup "^4.24.0" - source-map "0.8.0-beta.0" - sucrase "^3.35.0" - tinyexec "^0.3.1" - tinyglobby "^0.2.9" - tree-kill "^1.2.2" - tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -17287,11 +16842,6 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -17462,15 +17012,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - when@3.7.7: version "3.7.7" resolved "https://registry.yarnpkg.com/when/-/when-3.7.7.tgz#aba03fc3bb736d6c88b091d013d8a8e590d84718" From be91130f184f04c6e7bb9ff18222aba2ef2f96f6 Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 16 Feb 2025 15:14:43 -0600 Subject: [PATCH 33/73] chore: remove `devEngines` declaration in root package (#32398) This change removes the `devEngines` declaration in the root package. It didn't match the package.json spec and in npm 10.9.0 (released in October), a breaking change was introduced that checks the `devEngines` property. This causes `npm pack` calls to fail, due to the malformed `devEngines`. Since there's already an `.nvmrc` defined in the repo, and no strong need to enforce a specific node version for local development, this removes the declaration altogether. --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index 95fffa700a7e7..8e5dae3f944a0 100644 --- a/package.json +++ b/package.json @@ -106,9 +106,6 @@ "web-streams-polyfill": "^3.1.1", "yargs": "^15.3.1" }, - "devEngines": { - "node": "16.x || 18.x || 20.x || 22.x" - }, "jest": { "testRegex": "/scripts/jest/dont-run-jest-directly\\.js$" }, @@ -122,7 +119,7 @@ "lint": "node ./scripts/tasks/eslint.js", "lint-build": "node ./scripts/rollup/validate/index.js", "extract-errors": "node scripts/error-codes/extract-errors.js", - "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js", + "postinstall": "node ./scripts/flow/createFlowConfigs.js", "test": "node ./scripts/jest/jest-cli.js", "test-stable": "node ./scripts/jest/jest-cli.js --release-channel=stable", "test-www": "node ./scripts/jest/jest-cli.js --release-channel=www-modern", From eb1f77dedfc8f7491ecd39b160e4743fa39dfc99 Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 16 Feb 2025 16:00:43 -0600 Subject: [PATCH 34/73] ci: add workflow for running the eslint plugin e2e tests (#32397) This change adds a workflow for PR builds, that runs the e2e tests for `eslint-plugin-react-hooks` created in #32396 ![screenshot of ci tests running](https://github.com/user-attachments/assets/307a878c-92b5-44cf-84f2-3b21979b262a) --- .../workflows/runtime_eslint_plugin_e2e.yml | 56 +++++++++++++++++++ fixtures/eslint-v6/build.mjs | 9 ++- fixtures/eslint-v6/package.json | 2 +- fixtures/eslint-v6/yarn.lock | 2 +- fixtures/eslint-v7/build.mjs | 9 ++- fixtures/eslint-v7/package.json | 2 +- fixtures/eslint-v7/yarn.lock | 2 +- fixtures/eslint-v8/build.mjs | 9 ++- fixtures/eslint-v8/package.json | 2 +- fixtures/eslint-v8/yarn.lock | 2 +- fixtures/eslint-v9/build.mjs | 9 ++- fixtures/eslint-v9/package.json | 2 +- fixtures/eslint-v9/yarn.lock | 2 +- 13 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/runtime_eslint_plugin_e2e.yml diff --git a/.github/workflows/runtime_eslint_plugin_e2e.yml b/.github/workflows/runtime_eslint_plugin_e2e.yml new file mode 100644 index 0000000000000..a6f0bf28f005e --- /dev/null +++ b/.github/workflows/runtime_eslint_plugin_e2e.yml @@ -0,0 +1,56 @@ +name: (Runtime) ESLint Plugin E2E + +on: + push: + branches: [main] + pull_request: + paths-ignore: + - compiler/** + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +env: + TZ: /usr/share/zoneinfo/America/Los_Angeles + +jobs: + # ----- TESTS ----- + test: + name: ESLint v${{ matrix.eslint_major }} + runs-on: ubuntu-latest + strategy: + matrix: + eslint_major: + - "6" + - "7" + - "8" + - "9" + continue-on-error: true + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: yarn + cache-dependency-path: yarn.lock + - name: Restore cached node_modules + uses: actions/cache@v4 + id: node_modules + with: + path: "**/node_modules" + key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + - name: Ensure clean build directory + run: rm -rf build + - run: yarn install --frozen-lockfile + - name: Build plugin + working-directory: fixtures/eslint-v${{ matrix.eslint_major }} + run: node build.mjs + - name: Install fixture dependencies + working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }} + run: yarn --frozen-lockfile + - name: Run lint test + working-directory: ./fixtures/eslint-v${{ matrix.eslint_major }} + run: yarn lint diff --git a/fixtures/eslint-v6/build.mjs b/fixtures/eslint-v6/build.mjs index 02a1bbd25e00e..ebcea15190dae 100644 --- a/fixtures/eslint-v6/build.mjs +++ b/fixtures/eslint-v6/build.mjs @@ -1,5 +1,12 @@ #!/usr/bin/env node import {exec} from 'node:child_process'; +import {dirname, resolve} from 'node:path'; +import {fileURLToPath} from 'node:url'; -exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +exec('yarn build -r stable eslint-plugin-react-hooks', { + cwd: resolve(__dirname, '..', '..'), +}); diff --git a/fixtures/eslint-v6/package.json b/fixtures/eslint-v6/package.json index 50c92054b436f..c410edd78fc87 100644 --- a/fixtures/eslint-v6/package.json +++ b/fixtures/eslint-v6/package.json @@ -3,7 +3,7 @@ "name": "eslint-v6", "dependencies": { "eslint": "^6", - "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks" }, "scripts": { "build": "node build.mjs && yarn", diff --git a/fixtures/eslint-v6/yarn.lock b/fixtures/eslint-v6/yarn.lock index 3d6dbca037b38..5af9623e877f3 100644 --- a/fixtures/eslint-v6/yarn.lock +++ b/fixtures/eslint-v6/yarn.lock @@ -205,7 +205,7 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": +"eslint-plugin-react-hooks@link:../../build/oss-stable/eslint-plugin-react-hooks": version "0.0.0" uid "" diff --git a/fixtures/eslint-v7/build.mjs b/fixtures/eslint-v7/build.mjs index 02a1bbd25e00e..ebcea15190dae 100644 --- a/fixtures/eslint-v7/build.mjs +++ b/fixtures/eslint-v7/build.mjs @@ -1,5 +1,12 @@ #!/usr/bin/env node import {exec} from 'node:child_process'; +import {dirname, resolve} from 'node:path'; +import {fileURLToPath} from 'node:url'; -exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +exec('yarn build -r stable eslint-plugin-react-hooks', { + cwd: resolve(__dirname, '..', '..'), +}); diff --git a/fixtures/eslint-v7/package.json b/fixtures/eslint-v7/package.json index 90c369c603e6d..5e64917514599 100644 --- a/fixtures/eslint-v7/package.json +++ b/fixtures/eslint-v7/package.json @@ -3,7 +3,7 @@ "name": "eslint-v7", "dependencies": { "eslint": "^7", - "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks" }, "scripts": { "build": "node build.mjs && yarn", diff --git a/fixtures/eslint-v7/yarn.lock b/fixtures/eslint-v7/yarn.lock index 62f97bbc20d69..4a191de947f37 100644 --- a/fixtures/eslint-v7/yarn.lock +++ b/fixtures/eslint-v7/yarn.lock @@ -234,7 +234,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": +"eslint-plugin-react-hooks@link:../../build/oss-stable/eslint-plugin-react-hooks": version "0.0.0" uid "" diff --git a/fixtures/eslint-v8/build.mjs b/fixtures/eslint-v8/build.mjs index 02a1bbd25e00e..ebcea15190dae 100644 --- a/fixtures/eslint-v8/build.mjs +++ b/fixtures/eslint-v8/build.mjs @@ -1,5 +1,12 @@ #!/usr/bin/env node import {exec} from 'node:child_process'; +import {dirname, resolve} from 'node:path'; +import {fileURLToPath} from 'node:url'; -exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +exec('yarn build -r stable eslint-plugin-react-hooks', { + cwd: resolve(__dirname, '..', '..'), +}); diff --git a/fixtures/eslint-v8/package.json b/fixtures/eslint-v8/package.json index 620520ecdbfd4..ed3de107c524f 100644 --- a/fixtures/eslint-v8/package.json +++ b/fixtures/eslint-v8/package.json @@ -3,7 +3,7 @@ "name": "eslint-v8", "dependencies": { "eslint": "^8", - "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks" }, "scripts": { "build": "node build.mjs && yarn", diff --git a/fixtures/eslint-v8/yarn.lock b/fixtures/eslint-v8/yarn.lock index 88d04029954a4..d5a947b6347d7 100644 --- a/fixtures/eslint-v8/yarn.lock +++ b/fixtures/eslint-v8/yarn.lock @@ -192,7 +192,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": +"eslint-plugin-react-hooks@link:../../build/oss-stable/eslint-plugin-react-hooks": version "0.0.0" uid "" diff --git a/fixtures/eslint-v9/build.mjs b/fixtures/eslint-v9/build.mjs index 02a1bbd25e00e..ebcea15190dae 100644 --- a/fixtures/eslint-v9/build.mjs +++ b/fixtures/eslint-v9/build.mjs @@ -1,5 +1,12 @@ #!/usr/bin/env node import {exec} from 'node:child_process'; +import {dirname, resolve} from 'node:path'; +import {fileURLToPath} from 'node:url'; -exec('cd ../.. && yarn build -r stable eslint-plugin-react-hooks'); +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +exec('yarn build -r stable eslint-plugin-react-hooks', { + cwd: resolve(__dirname, '..', '..'), +}); diff --git a/fixtures/eslint-v9/package.json b/fixtures/eslint-v9/package.json index e8ae91a0613e1..58d0a94c0c58c 100644 --- a/fixtures/eslint-v9/package.json +++ b/fixtures/eslint-v9/package.json @@ -3,7 +3,7 @@ "name": "eslint-v9", "dependencies": { "eslint": "^9", - "eslint-plugin-react-hooks": "link:../../build/node_modules/eslint-plugin-react-hooks" + "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks" }, "scripts": { "build": "node build.mjs && yarn", diff --git a/fixtures/eslint-v9/yarn.lock b/fixtures/eslint-v9/yarn.lock index 5a1a246827f6f..1583b73f14e21 100644 --- a/fixtures/eslint-v9/yarn.lock +++ b/fixtures/eslint-v9/yarn.lock @@ -209,7 +209,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -"eslint-plugin-react-hooks@link:../../build/node_modules/eslint-plugin-react-hooks": +"eslint-plugin-react-hooks@link:../../build/oss-stable/eslint-plugin-react-hooks": version "0.0.0" uid "" From 4632e36a4ef16a1af24987c56e42b664f6403e64 Mon Sep 17 00:00:00 2001 From: michael faith Date: Sun, 16 Feb 2025 19:28:12 -0600 Subject: [PATCH 35/73] refactor(eslint-plugin-react-hooks): change array type and improve conditionals (#32400) - [build(eslint-plugin-react-hooks): add ts-linting](https://github.com/facebook/react/commit/4c0fbe73d9abc2681445f62b9450737f3df12ee2) This change adds configuration to the eslint config governing `eslint-plugin-react-hooks` to use the typescript-eslint plugin and parser. It adds the typescript-recommended config, and configures the team's preferred `array-type` convention. - [refactor(eslint-plugin-react-hooks): improve conditionals](https://github.com/facebook/react/commit/540d0d95bc5172ef95ccc2ad70b4b202b6eeedd2) This change addresses several feedback items from https://github.com/facebook/react/pull/32240 - [ci (eslint-e2e): exclude nested node_modules from cache](https://github.com/facebook/react/pull/32400/commits/a3279f46a85cfb4ddea5a863a6f7c71344280d36) This change removes the nested fixture `node_modules` from being cached, so that the symbolic link can be made after the build happens. --- .eslintrc.js | 25 ++++++++-- .../workflows/runtime_eslint_plugin_e2e.yml | 2 +- package.json | 2 + .../src/ExhaustiveDeps.ts | 32 ++++++------ .../src/RulesOfHooks.ts | 12 +++-- yarn.lock | 50 ++++++++++++++++++- 6 files changed, 97 insertions(+), 26 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 7fe08f4cdf36e..360bd5ba76a9c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -446,10 +446,7 @@ module.exports = { }, }, { - files: [ - 'scripts/eslint-rules/*.js', - 'packages/eslint-plugin-react-hooks/src/*.js', - ], + files: ['scripts/eslint-rules/*.js'], plugins: ['eslint-plugin'], rules: { 'eslint-plugin/prefer-object-rule': ERROR, @@ -517,6 +514,26 @@ module.exports = { __IS_INTERNAL_VERSION__: 'readonly', }, }, + { + files: ['packages/eslint-plugin-react-hooks/src/**/*'], + extends: ['plugin:@typescript-eslint/recommended'], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'eslint-plugin'], + rules: { + '@typescript-eslint/no-explicit-any': OFF, + '@typescript-eslint/no-non-null-assertion': OFF, + '@typescript-eslint/array-type': [ERROR, {default: 'generic'}], + + 'es/no-optional-chaining': OFF, + + 'eslint-plugin/prefer-object-rule': ERROR, + 'eslint-plugin/require-meta-fixable': [ + ERROR, + {catchNoFixerButFixableProperty: true}, + ], + 'eslint-plugin/require-meta-has-suggestions': ERROR, + }, + }, ], env: { diff --git a/.github/workflows/runtime_eslint_plugin_e2e.yml b/.github/workflows/runtime_eslint_plugin_e2e.yml index a6f0bf28f005e..edc188f38622e 100644 --- a/.github/workflows/runtime_eslint_plugin_e2e.yml +++ b/.github/workflows/runtime_eslint_plugin_e2e.yml @@ -40,7 +40,7 @@ jobs: uses: actions/cache@v4 id: node_modules with: - path: "**/node_modules" + path: "node_modules" key: runtime-eslint_e2e-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} - name: Ensure clean build directory run: rm -rf build diff --git a/package.json b/package.json index 8e5dae3f944a0..0a2b6c3024c93 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-typescript": "^12.1.2", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "abortcontroller-polyfill": "^1.7.5", "art": "0.10.1", "babel-plugin-syntax-trailing-function-commas": "^6.5.0", diff --git a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts index 9f3d4d4db9004..1b0059757278f 100644 --- a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts +++ b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts @@ -28,7 +28,7 @@ type DeclaredDependency = { type Dependency = { isStable: boolean; - references: Scope.Reference[]; + references: Array; }; type DependencyTreeNode = { @@ -184,7 +184,9 @@ const rule = { // Get the current scope. const scope = scopeManager.acquire(node); if (!scope) { - return; + throw new Error( + 'Unable to acquire scope for the current node. This is a bug in eslint-plugin-react-hooks, please file an issue.', + ); } // Find all our "pure scopes". On every re-render of a component these @@ -255,7 +257,7 @@ const rule = { // Detect primitive constants // const foo = 42 let declaration = defNode.parent; - if (declaration == null && componentScope) { + if (declaration == null && componentScope != null) { // This might happen if variable is declared after the callback. // In that case ESLint won't set up .parent refs. // So we'll set them up manually. @@ -266,7 +268,7 @@ const rule = { } } if ( - declaration && + declaration != null && 'kind' in declaration && declaration.kind === 'const' && init.type === 'Literal' && @@ -454,7 +456,7 @@ const rule = { function isInsideEffectCleanup(reference: Scope.Reference): boolean { let curScope: Scope.Scope | null = reference.from; let isInReturnedFunction = false; - while (curScope && curScope.block !== node) { + while (curScope != null && curScope.block !== node) { if (curScope.type === 'function') { isInReturnedFunction = curScope.block.parent != null && @@ -529,7 +531,7 @@ const rule = { continue; } // Ignore references to the function itself as it's not defined yet. - if (def.node && def.node.init === node.parent) { + if (def.node != null && def.node.init === node.parent) { continue; } // Ignore Flow type parameters @@ -566,8 +568,8 @@ const rule = { // Is React managing this ref or us? // Let's see if we can find a .current assignment. let foundCurrentAssignment = false; - for (const reference of references) { - const {identifier} = reference; + for (const ref of references) { + const {identifier} = ref; const {parent} = identifier; if ( parent != null && @@ -660,7 +662,7 @@ const rule = { } let fnScope: Scope.Scope | null = reference.from; - while (fnScope && fnScope.type !== 'function') { + while (fnScope != null && fnScope.type !== 'function') { fnScope = fnScope.upper; } const isDirectlyInsideEffect = fnScope?.block === node; @@ -704,7 +706,7 @@ const rule = { return; } - const declaredDependencies: DeclaredDependency[] = []; + const declaredDependencies: Array = []; const externalDependencies = new Set(); const isArrayExpression = declaredDependenciesNode.type === 'ArrayExpression'; @@ -1469,7 +1471,7 @@ function collectRecommendations({ isEffect, }: { dependencies: Map; - declaredDependencies: DeclaredDependency[]; + declaredDependencies: Array; stableDependencies: Set; externalDependencies: Set; isEffect: boolean; @@ -1592,7 +1594,7 @@ function collectRecommendations({ } // Collect suggestions in the order they were originally specified. - const suggestedDependencies: string[] = []; + const suggestedDependencies: Array = []; const unnecessaryDependencies = new Set(); const duplicateDependencies = new Set(); declaredDependencies.forEach(({key}) => { @@ -1699,7 +1701,7 @@ function scanForConstructions({ componentScope, scope, }: { - declaredDependencies: DeclaredDependency[]; + declaredDependencies: Array; declaredDependenciesNode: Node; componentScope: Scope.Scope; scope: Scope.Scope; @@ -1747,7 +1749,7 @@ function scanForConstructions({ } return null; }) - .filter(Boolean) as [Scope.Variable, string][]; + .filter(Boolean) as Array<[Scope.Variable, string]>; function isUsedOutsideOfHook(ref: Scope.Variable): boolean { let foundWriteExpr = false; @@ -2007,7 +2009,7 @@ function fastFindReferenceWithParent(start: Node, target: Node): Node | null { return null; } -function joinEnglish(arr: string[]): string { +function joinEnglish(arr: Array): string { let s = ''; for (let i = 0; i < arr.length; i++) { s += arr[i]; diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts index 24870d71614ea..0ab0c5ff21e4b 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts @@ -130,8 +130,10 @@ const rule = { }, create(context: Rule.RuleContext) { let lastEffect: CallExpression | null = null; - const codePathReactHooksMapStack: Map[] = []; - const codePathSegmentStack: Rule.CodePathSegment[] = []; + const codePathReactHooksMapStack: Array< + Map> + > = []; + const codePathSegmentStack: Array = []; const useEffectEventFunctions = new WeakSet(); // For a given scope, iterate through the references and add all useEffectEvent definitions. We can @@ -190,7 +192,7 @@ const rule = { // Maintain code path stack as we traverse. onCodePathStart: () => codePathReactHooksMapStack.push( - new Map(), + new Map>(), ), // Process our code path. @@ -561,7 +563,7 @@ const rule = { context.report({node: hook, message}); } } else if ( - codePathNode.parent && + codePathNode.parent != null && (codePathNode.parent.type === 'MethodDefinition' || // @ts-expect-error `ClassProperty` was removed from typescript-estree in https://github.com/typescript-eslint/typescript-eslint/pull/3806 codePathNode.parent.type === 'ClassProperty' || @@ -758,7 +760,7 @@ function getFunctionName(node: Node) { /** * Convenience function for peeking the last item in a stack. */ -function last(array: T[]): T { +function last(array: Array): T { return array[array.length - 1] as T; } diff --git a/yarn.lock b/yarn.lock index ba71ba7abe323..7bc936afae4bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2410,6 +2410,11 @@ dependencies: eslint-visitor-keys "^3.3.0" +"@eslint-community/regexpp@^4.5.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + "@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" @@ -3718,6 +3723,23 @@ dependencies: "@types/node" "*" +"@typescript-eslint/eslint-plugin@^6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/experimental-utils@2.34.0": version "2.34.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" @@ -3780,6 +3802,17 @@ "@typescript-eslint/typescript-estree" "5.62.0" debug "^4.3.4" +"@typescript-eslint/parser@^6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + "@typescript-eslint/scope-manager@4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.1.0.tgz#9e389745ee9cfe12252ed1e9958808abd6b3a683" @@ -3804,6 +3837,16 @@ "@typescript-eslint/types" "6.21.0" "@typescript-eslint/visitor-keys" "6.21.0" +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== + dependencies: + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/types@3.10.1": version "3.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" @@ -3892,7 +3935,7 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@^6.0.0": +"@typescript-eslint/utils@6.21.0", "@typescript-eslint/utils@^6.0.0": version "6.21.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== @@ -9799,6 +9842,11 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +ignore@^5.2.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + image-size@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.1.1.tgz#ddd67d4dc340e52ac29ce5f546a09f4e29e840ac" From 8a7b487e3b171c91f2fe18e9142af53f4dd83454 Mon Sep 17 00:00:00 2001 From: Ricky Date: Tue, 18 Feb 2025 10:29:40 -0500 Subject: [PATCH 36/73] [flags] enable owner stacks everywhere (#32376) this is now canary and on everywhere --- .../shared/forks/ReactFeatureFlags.native-fb-dynamic.js | 2 -- packages/shared/forks/ReactFeatureFlags.native-fb.js | 6 ++++-- packages/shared/forks/ReactFeatureFlags.native-oss.js | 4 ++-- .../forks/ReactFeatureFlags.test-renderer.native-fb.js | 4 ++-- .../shared/forks/ReactFeatureFlags.test-renderer.www.js | 4 ++-- packages/shared/forks/ReactFeatureFlags.www-dynamic.js | 2 -- packages/shared/forks/ReactFeatureFlags.www.js | 6 ++++-- scripts/jest/setupTests.www.js | 1 + scripts/jest/setupTests.xplat.js | 2 ++ 9 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js index ffcec9334d663..d3c5de81bd909 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js @@ -25,7 +25,5 @@ export const enableShallowPropDiffing = __VARIANT__; export const passChildrenWhenCloningPersistedNodes = __VARIANT__; export const enableSiblingPrerendering = __VARIANT__; export const enableUseEffectCRUDOverload = __VARIANT__; -export const enableOwnerStacks = __VARIANT__; -export const enableRemoveConsolePatches = __VARIANT__; export const enableFastAddPropertiesInDiffing = __VARIANT__; export const enableLazyPublicInstanceInFabric = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 470e148959327..a0544dc161173 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -27,12 +27,14 @@ export const { enableUseEffectCRUDOverload, passChildrenWhenCloningPersistedNodes, enableSiblingPrerendering, - enableOwnerStacks, - enableRemoveConsolePatches, enableFastAddPropertiesInDiffing, enableLazyPublicInstanceInFabric, } = dynamicFlags; +// These two can be removed +export const enableOwnerStacks = true; +export const enableRemoveConsolePatches = true; + // The rest of the flags are static for better dead code elimination. export const disableClientCache = true; export const disableCommentsAsDOMContainers = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 361ebde4136ce..fd846cf312212 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -40,7 +40,7 @@ export const enableLegacyFBSupport = false; export const enableLegacyHidden = false; export const enableNoCloningMemoCache = false; export const enableObjectFiber = false; -export const enableOwnerStacks = false; +export const enableOwnerStacks = true; export const enablePersistedModeClonedFlag = false; export const enablePostpone = false; export const enableReactTestRendererWarning = false; @@ -80,7 +80,7 @@ export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = __PROFILE__; export const enableProfilerNestedUpdatePhase = __PROFILE__; export const enableUpdaterTracking = __PROFILE__; -export const enableRemoveConsolePatches = false; +export const enableRemoveConsolePatches = true; // Flow magic to verify the exports of this file match the original version. ((((null: any): ExportsType): FeatureFlagsType): ExportsType); diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 18172fdfe5724..e4f1210c29f00 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -35,7 +35,7 @@ export const enableLegacyFBSupport = false; export const enableLegacyHidden = false; export const enableNoCloningMemoCache = false; export const enableObjectFiber = false; -export const enableOwnerStacks = false; +export const enableOwnerStacks = true; export const enablePersistedModeClonedFlag = false; export const enablePostpone = false; export const enableProfilerCommitHooks = __PROFILE__; @@ -67,8 +67,8 @@ export const enableHydrationLaneScheduling = true; export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; export const enableViewTransition = false; +export const enableRemoveConsolePatches = true; export const enableSwipeTransition = false; -export const enableRemoveConsolePatches = false; export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 628a834133f2f..112b30fe04a81 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -70,7 +70,7 @@ export const disableDefaultPropsExceptForClasses = true; export const renameElementSymbol = false; export const enableObjectFiber = false; -export const enableOwnerStacks = false; +export const enableOwnerStacks = true; export const enableShallowPropDiffing = false; export const enableSiblingPrerendering = true; @@ -82,8 +82,8 @@ export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; export const enableViewTransition = false; +export const enableRemoveConsolePatches = true; export const enableSwipeTransition = false; -export const enableRemoveConsolePatches = false; export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index d0b4404a3e946..e0f431fe7f10c 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -26,7 +26,6 @@ export const enableRetryLaneExpiration = __VARIANT__; export const enableTransitionTracing = __VARIANT__; export const favorSafetyOverHydrationPerf = __VARIANT__; export const renameElementSymbol = __VARIANT__; -export const enableOwnerStacks = __VARIANT__; export const retryLaneExpirationMs = 5000; export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; @@ -37,7 +36,6 @@ export const enableInfiniteRenderLoopDetection = __VARIANT__; export const enableSiblingPrerendering = __VARIANT__; export const enableUseEffectCRUDOverload = __VARIANT__; -export const enableRemoveConsolePatches = __VARIANT__; export const enableFastAddPropertiesInDiffing = __VARIANT__; export const enableLazyPublicInstanceInFabric = false; export const enableViewTransition = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 5932c6eddf6f1..ec0337382e957 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -35,8 +35,6 @@ export const { retryLaneExpirationMs, syncLaneExpirationMs, transitionLaneExpirationMs, - enableOwnerStacks, - enableRemoveConsolePatches, enableFastAddPropertiesInDiffing, enableViewTransition, } = dynamicFeatureFlags; @@ -44,6 +42,10 @@ export const { // On WWW, __EXPERIMENTAL__ is used for a new modern build. // It's not used anywhere in production yet. +// Can remove these two +export const enableOwnerStacks = true; +export const enableRemoveConsolePatches = true; + export const enableProfilerTimer = __PROFILE__; export const enableProfilerCommitHooks = __PROFILE__; export const enableProfilerNestedUpdatePhase = __PROFILE__; diff --git a/scripts/jest/setupTests.www.js b/scripts/jest/setupTests.www.js index b0b653bb78ff9..9ac7ffc6db002 100644 --- a/scripts/jest/setupTests.www.js +++ b/scripts/jest/setupTests.www.js @@ -11,6 +11,7 @@ jest.mock('shared/ReactFeatureFlags', () => { // Flags that aren't currently used, but we still want to force variants to keep the // code live. actual.disableInputAttributeSyncing = __VARIANT__; + actual.enableOwnerStacks = __VARIANT__; // These are hardcoded to true for the next release, // but still run the tests against both variants until diff --git a/scripts/jest/setupTests.xplat.js b/scripts/jest/setupTests.xplat.js index 859a5065988d6..87fbf8bef8ac5 100644 --- a/scripts/jest/setupTests.xplat.js +++ b/scripts/jest/setupTests.xplat.js @@ -11,6 +11,8 @@ jest.mock('shared/ReactFeatureFlags', () => { 'shared/forks/ReactFeatureFlags.native-fb.js' ); + actual.enableOwnerStacks = __VARIANT__; + // Lots of tests use these, but we don't want to expose it to RN. // Ideally, tests for xplat wouldn't use react-dom, but many of our tests do. // Since the xplat tests run with the www entry points, some of these flags From d99f8bba2e07e3bb953f0821d4da5e341136fe5c Mon Sep 17 00:00:00 2001 From: mofeiZ <34200447+mofeiZ@users.noreply.github.com> Date: Tue, 18 Feb 2025 09:32:49 -0700 Subject: [PATCH 37/73] [compiler] Delete LoweredFunction.dependencies and hoisted instructions (#32096) LoweredFunction dependencies were exclusively used for dependency extraction (in `propagateScopeDeps`). Now that we have a `propagateScopeDepsHIR` that recursively traverses into nested functions, we can delete `dependencies` and their associated synthetic `LoadLocal`/`PropertyLoad` instructions. [Internal snapshot diff](https://www.internalfb.com/phabricator/paste/view/P1716950202) for this change shows ~.2% of files changed. I [read through ~60 of the changed files](https://www.internalfb.com/phabricator/paste/view/P1733074307) - most changes are due to better outlining (due to better DCE) - a few changes in memo inference are due to changed ordering ``` // source arr.map(() => contextVar.inner); // previous instructions $0 = LoadLocal arr $1 = $0.map // Below instructions are synthetic $2 = LoadLocal contextVar $3 = $2.inner $4 = Function deps=$3 context=contextVar { ... } ``` - a few changes are effectively bugfixes (see `aliased-nested-scope-fn-expr`) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32096). * #32099 * #32286 * #32104 * #32098 * #32097 * __->__ #32096 --- .../src/HIR/BuildHIR.ts | 154 +----------- .../src/HIR/CollectHoistablePropertyLoads.ts | 37 +-- .../src/HIR/Environment.ts | 2 - .../src/HIR/HIR.ts | 1 - .../HIR/MergeOverlappingReactiveScopesHIR.ts | 17 +- .../src/HIR/PrintHIR.ts | 5 +- .../src/HIR/PropagateScopeDependenciesHIR.ts | 5 +- .../src/HIR/visitors.ts | 7 +- .../src/Inference/AnalyseFunctions.ts | 146 +++--------- .../Inference/InferMutableContextVariables.ts | 23 +- .../src/Inference/InferReferenceEffects.ts | 48 ++-- .../src/Optimization/LowerContextAccess.ts | 1 - .../src/Optimization/OutlineFunctions.ts | 1 - .../InferReactiveScopeVariables.ts | 8 + .../src/SSA/EliminateRedundantPhi.ts | 19 ++ .../src/SSA/EnterSSA.ts | 3 - .../src/Transform/TransformFire.ts | 53 +---- .../aliased-nested-scope-fn-expr.expect.md | 120 ++++++++++ .../compiler/aliased-nested-scope-fn-expr.tsx | 46 ++++ ...iased-nested-scope-truncated-dep.expect.md | 221 ++++++++++++++++++ .../aliased-nested-scope-truncated-dep.tsx | 93 ++++++++ ...access-in-unused-callback-nested.expect.md | 40 ++-- .../capturing-func-mutate-2.expect.md | 1 - .../capturing-func-no-mutate.expect.md | 12 +- ...capturing-func-simple-alias-iife.expect.md | 1 - ...ction-alias-computed-load-2-iife.expect.md | 1 - ...ction-alias-computed-load-3-iife.expect.md | 2 - ...ction-alias-computed-load-4-iife.expect.md | 1 - ...unction-alias-computed-load-iife.expect.md | 1 - ...capturing-reference-changes-type.expect.md | 1 - .../codegen-inline-iife-reassign.expect.md | 3 +- ...-into-function-expression-global.expect.md | 7 +- ...to-function-expression-primitive.expect.md | 7 +- ...gation-into-function-expressions.expect.md | 9 +- ...text-variable-as-jsx-element-tag.expect.md | 3 +- ...ting-simple-function-declaration.expect.md | 10 +- ...call-freezes-captured-identifier.expect.md | 41 ++++ ....hook-call-freezes-captured-identifier.tsx | 20 ++ ...call-freezes-captured-memberexpr.expect.md | 41 ++++ ....hook-call-freezes-captured-memberexpr.jsx | 20 ++ ...on-with-shadowed-local-same-name.expect.md | 2 +- ...setstate-captured-indirectly-jsx.expect.md | 81 +++++++ ...isting-setstate-captured-indirectly-jsx.js | 17 ++ .../compiler/hoisting-setstate.expect.md | 77 ++++++ .../fixtures/compiler/hoisting-setstate.js | 28 +++ ...call-freezes-captured-memberexpr.expect.md | 87 +++++++ .../hook-call-freezes-captured-memberexpr.tsx | 21 ++ .../jsx-local-tag-in-lambda.expect.md | 7 +- .../jsx-memberexpr-tag-in-lambda.expect.md | 7 +- ...mutated-non-reactive-to-reactive.expect.md | 1 - .../lambda-mutated-ref-non-reactive.expect.md | 1 - ...ed-function-shadowed-identifiers.expect.md | 5 +- ...o-reordering-depslist-assignment.expect.md | 1 - ...e-phis-in-lambda-capture-context.expect.md | 29 +-- .../use-operator-conditional.expect.md | 1 - 55 files changed, 1123 insertions(+), 473 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.jsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index d5c3ea6a492a9..67cdcab0381f2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -7,7 +7,6 @@ import {NodePath, Scope} from '@babel/traverse'; import * as t from '@babel/types'; -import {Expression} from '@babel/types'; import invariant from 'invariant'; import { CompilerError, @@ -75,7 +74,7 @@ export function lower( parent: NodePath | null = null, ): Result { const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs); - const context: Array = []; + const context: HIRFunction['context'] = []; for (const ref of capturedRefs ?? []) { context.push({ @@ -3378,7 +3377,7 @@ function lowerFunction( >, ): LoweredFunction | null { const componentScope: Scope = builder.parentFunction.scope; - const captured = gatherCapturedDeps(builder, expr, componentScope); + const capturedContext = gatherCapturedContext(expr, componentScope); /* * TODO(gsn): In the future, we could only pass in the context identifiers @@ -3392,7 +3391,7 @@ function lowerFunction( expr, builder.environment, builder.bindings, - [...builder.context, ...captured.identifiers], + [...builder.context, ...capturedContext], builder.parentFunction, ); let loweredFunc: HIRFunction; @@ -3405,7 +3404,6 @@ function lowerFunction( loweredFunc = lowering.unwrap(); return { func: loweredFunc, - dependencies: captured.refs, }; } @@ -4079,14 +4077,6 @@ function lowerAssignment( } } -function isValidDependency(path: NodePath): boolean { - const parent: NodePath = path.parentPath; - return ( - !path.node.computed && - !(parent.isCallExpression() && parent.get('callee') === path) - ); -} - function captureScopes({from, to}: {from: Scope; to: Scope}): Set { let scopes: Set = new Set(); while (from) { @@ -4101,8 +4091,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set { return scopes; } -function gatherCapturedDeps( - builder: HIRBuilder, +function gatherCapturedContext( fn: NodePath< | t.FunctionExpression | t.ArrowFunctionExpression @@ -4110,10 +4099,8 @@ function gatherCapturedDeps( | t.ObjectMethod >, componentScope: Scope, -): {identifiers: Array; refs: Array} { - const capturedIds: Map = new Map(); - const capturedRefs: Set = new Set(); - const seenPaths: Set = new Set(); +): Array { + const capturedIds = new Set(); /* * Capture all the scopes from the parent of this function up to and including @@ -4124,33 +4111,11 @@ function gatherCapturedDeps( to: componentScope, }); - function addCapturedId(bindingIdentifier: t.Identifier): number { - if (!capturedIds.has(bindingIdentifier)) { - const index = capturedIds.size; - capturedIds.set(bindingIdentifier, index); - return index; - } else { - return capturedIds.get(bindingIdentifier)!; - } - } - function handleMaybeDependency( - path: - | NodePath - | NodePath - | NodePath, + path: NodePath | NodePath, ): void { // Base context variable to depend on let baseIdentifier: NodePath | NodePath; - /* - * Base expression to depend on, which (for now) may contain non side-effectful - * member expressions - */ - let dependency: - | NodePath - | NodePath - | NodePath - | NodePath; if (path.isJSXOpeningElement()) { const name = path.get('name'); if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) { @@ -4166,115 +4131,20 @@ function gatherCapturedDeps( 'Invalid logic in gatherCapturedDeps', ); baseIdentifier = current; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if (nextDep && nextDep.isJSXMemberExpression()) { - currentDep = nextDep; - } else { - break; - } - } - dependency = currentDep; - } else if (path.isMemberExpression()) { - // Calculate baseIdentifier - let currentId: NodePath = path; - while (currentId.isMemberExpression()) { - currentId = currentId.get('object'); - } - if (!currentId.isIdentifier()) { - return; - } - baseIdentifier = currentId; - - /* - * Get the expression to depend on, which may involve PropertyLoads - * for member expressions - */ - let currentDep: - | NodePath - | NodePath - | NodePath = baseIdentifier; - - while (true) { - const nextDep: null | NodePath = currentDep.parentPath; - if ( - nextDep && - nextDep.isMemberExpression() && - isValidDependency(nextDep) - ) { - currentDep = nextDep; - } else { - break; - } - } - - dependency = currentDep; } else { baseIdentifier = path; - dependency = path; } /* * Skip dependency path, as we already tried to recursively add it (+ all subexpressions) * as a dependency. */ - dependency.skip(); + path.skip(); // Add the base identifier binding as a dependency. const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name); - if (binding === undefined || !pureScopes.has(binding.scope)) { - return; - } - const idKey = String(addCapturedId(binding.identifier)); - - // Add the expression (potentially a memberexpr path) as a dependency. - let exprKey = idKey; - if (dependency.isMemberExpression()) { - let pathTokens = []; - let current: NodePath = dependency; - while (current.isMemberExpression()) { - const property = current.get('property') as NodePath; - pathTokens.push(property.node.name); - current = current.get('object'); - } - - exprKey += '.' + pathTokens.reverse().join('.'); - } else if (dependency.isJSXMemberExpression()) { - let pathTokens = []; - let current: NodePath = - dependency; - while (current.isJSXMemberExpression()) { - const property = current.get('property'); - pathTokens.push(property.node.name); - current = current.get('object'); - } - } - - if (!seenPaths.has(exprKey)) { - let loweredDep: Place; - if (dependency.isJSXIdentifier()) { - loweredDep = lowerValueToTemporary(builder, { - kind: 'LoadLocal', - place: lowerIdentifier(builder, dependency), - loc: path.node.loc ?? GeneratedSource, - }); - } else if (dependency.isJSXMemberExpression()) { - loweredDep = lowerJsxMemberExpression(builder, dependency); - } else { - loweredDep = lowerExpressionToTemporary(builder, dependency); - } - capturedRefs.add(loweredDep); - seenPaths.add(exprKey); + if (binding !== undefined && pureScopes.has(binding.scope)) { + capturedIds.add(binding.identifier); } } @@ -4305,13 +4175,13 @@ function gatherCapturedDeps( return; } else if (path.isJSXElement()) { handleMaybeDependency(path.get('openingElement')); - } else if (path.isMemberExpression() || path.isIdentifier()) { + } else if (path.isIdentifier()) { handleMaybeDependency(path); } }, }); - return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]}; + return [...capturedIds.keys()]; } function notNull(value: T | null): value is T { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index d3c919a6d8afe..a422570fffead 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -131,15 +131,7 @@ function collectHoistablePropertyLoadsImpl( fn: HIRFunction, context: CollectHoistablePropertyLoadsContext, ): ReadonlyMap { - const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn); - const actuallyEvaluatedTemporaries = new Map( - [...context.temporaries].filter(([id]) => !functionExpressionLoads.has(id)), - ); - - const nodes = collectNonNullsInBlocks(fn, { - ...context, - temporaries: actuallyEvaluatedTemporaries, - }); + const nodes = collectNonNullsInBlocks(fn, context); propagateNonNull(fn, nodes, context.registry); if (DEBUG_PRINT) { @@ -598,30 +590,3 @@ function reduceMaybeOptionalChains( } } while (changed); } - -function collectFunctionExpressionFakeLoads( - fn: HIRFunction, -): Set { - const sources = new Map(); - const functionExpressionReferences = new Set(); - - for (const [_, block] of fn.body.blocks) { - for (const {lvalue, value} of block.instructions) { - if ( - value.kind === 'FunctionExpression' || - value.kind === 'ObjectMethod' - ) { - for (const reference of value.loweredFunc.dependencies) { - let curr: IdentifierId | undefined = reference.identifier.id; - while (curr != null) { - functionExpressionReferences.add(curr); - curr = sources.get(curr); - } - } - } else if (value.kind === 'PropertyLoad') { - sources.set(lvalue.identifier.id, value.object.identifier.id); - } - } - } - return functionExpressionReferences; -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 113af2025dd2b..8de9218a662e2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -245,8 +245,6 @@ const EnvironmentConfigSchema = z.object({ */ enableUseTypeAnnotations: z.boolean().default(false), - enableFunctionDependencyRewrite: z.boolean().default(true), - /** * Enables inference of optional dependency chains. Without this flag * a property chain such as `props?.items?.foo` will infer as a dep on diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index a6a9cbcd48ec5..4df6da6c1704c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -722,7 +722,6 @@ export type ObjectProperty = { }; export type LoweredFunction = { - dependencies: Array; func: HIRFunction; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts index a3740539b295b..5b286e917d0d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/MergeOverlappingReactiveScopesHIR.ts @@ -148,6 +148,14 @@ function collectScopeInfo(fn: HIRFunction): ScopeInfo { const scope = place.identifier.scope; if (scope != null) { placeScopes.set(place, scope); + /** + * Record both mutating and non-mutating scopes to merge scopes with + * still-mutating values with inner scopes that alias those values + * (see `nonmutating-capture-in-unsplittable-memo-block`) + * + * Note that this isn't perfect, as it also leads to merging of mutating + * scopes with JSX single-instruction scopes (see `mutation-within-jsx`) + */ if (scope.range.start !== scope.range.end) { getOrInsertDefault(scopeStarts, scope.range.start, new Set()).add( scope, @@ -254,7 +262,7 @@ function visitPlace( * of the stack to the mutated outer scope. */ const placeScope = getPlaceScope(id, place); - if (placeScope != null && isMutable({id} as any, place)) { + if (placeScope != null && isMutable({id}, place)) { const placeScopeIdx = activeScopes.indexOf(placeScope); if (placeScopeIdx !== -1 && placeScopeIdx !== activeScopes.length - 1) { joined.union([placeScope, ...activeScopes.slice(placeScopeIdx + 1)]); @@ -275,6 +283,13 @@ function getOverlappingReactiveScopes( for (const instr of block.instructions) { visitInstructionId(instr.id, context, state); for (const place of eachInstructionOperand(instr)) { + if ( + (instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod') && + place.identifier.type.kind === 'Primitive' + ) { + continue; + } visitPlace(instr.id, place, state); } for (const place of eachInstructionLValue(instr)) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index a6f6c606e118d..7a6586730f9c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string { .split('\n') .map(line => ` ${line}`) .join('\n'); - const deps = instrValue.loweredFunc.dependencies - .map(dep => printPlace(dep)) - .join(','); const context = instrValue.loweredFunc.func.context .map(dep => printPlace(dep)) .join(','); @@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string { }) .join(', ') ?? ''; const type = printType(instrValue.loweredFunc.func.returnType).trim(); - value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; + value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`; break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts index 08856e9143139..4cb84870a8a33 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PropagateScopeDependenciesHIR.ts @@ -738,9 +738,8 @@ function collectDependencies( } for (const instr of block.instructions) { if ( - fn.env.config.enableFunctionDependencyRewrite && - (instr.value.kind === 'FunctionExpression' || - instr.value.kind === 'ObjectMethod') + instr.value.kind === 'FunctionExpression' || + instr.value.kind === 'ObjectMethod' ) { context.declare(instr.lvalue.identifier, { id: instr.id, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index c9ee803bfaffd..49ff3c256e016 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -193,7 +193,7 @@ export function* eachInstructionValueOperand( } case 'ObjectMethod': case 'FunctionExpression': { - yield* instrValue.loweredFunc.dependencies; + yield* instrValue.loweredFunc.func.context; break; } case 'TaggedTemplateExpression': { @@ -517,8 +517,9 @@ export function mapInstructionValueOperands( } case 'ObjectMethod': case 'FunctionExpression': { - instrValue.loweredFunc.dependencies = - instrValue.loweredFunc.dependencies.map(d => fn(d)); + instrValue.loweredFunc.func.context = + instrValue.loweredFunc.func.context.map(d => fn(d)); + break; } case 'TaggedTemplateExpression': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 75bd8f6811ffb..5381262874369 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,9 +10,8 @@ import { Effect, HIRFunction, Identifier, - IdentifierName, + IdentifierId, LoweredFunction, - Place, isRefOrRefValue, makeInstructionId, } from '../HIR'; @@ -23,78 +22,39 @@ import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; -type Dependency = { - identifier: Identifier; - path: Array; -}; - // Helper class to track indirections such as LoadLocal and PropertyLoad. export class IdentifierState { - properties: Map = new Map(); + properties: Map = new Map(); resolve(identifier: Identifier): Identifier { - const resolved = this.properties.get(identifier); + const resolved = this.properties.get(identifier.id); if (resolved !== undefined) { - return resolved.identifier; + return resolved; } return identifier; } - declareProperty(lvalue: Place, object: Place, property: string): void { - const objectDependency = this.properties.get(object.identifier); - let nextDependency: Dependency; - if (objectDependency === undefined) { - nextDependency = {identifier: object.identifier, path: [property]}; - } else { - nextDependency = { - identifier: objectDependency.identifier, - path: [...objectDependency.path, property], - }; - } - this.properties.set(lvalue.identifier, nextDependency); - } - - declareTemporary(lvalue: Place, value: Place): void { - const resolved: Dependency = this.properties.get(value.identifier) ?? { - identifier: value.identifier, - path: [], - }; - this.properties.set(lvalue.identifier, resolved); + alias(lvalue: Identifier, value: Identifier): void { + this.properties.set(lvalue.id, this.properties.get(value.id) ?? value); } } export default function analyseFunctions(func: HIRFunction): void { - const state = new IdentifierState(); - for (const [_, block] of func.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { case 'ObjectMethod': case 'FunctionExpression': { lower(instr.value.loweredFunc.func); - infer(instr.value.loweredFunc, state, func.context); - break; - } - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } - case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. + infer(instr.value.loweredFunc); + + /** + * Reset mutable range for outer inferReferenceEffects */ - state.declareProperty(instr.lvalue, instr.value.object, ''); - break; - } - case 'LoadLocal': - case 'LoadContext': { - if (instr.lvalue.identifier.name === null) { - state.declareTemporary(instr.lvalue, instr.value.place); + for (const operand of instr.value.loweredFunc.func.context) { + operand.identifier.mutableRange.start = makeInstructionId(0); + operand.identifier.mutableRange.end = makeInstructionId(0); + operand.identifier.scope = null; } break; } @@ -110,7 +70,6 @@ function lower(func: HIRFunction): void { inferMutableRanges(func); rewriteInstructionKindsBasedOnReassignment(func); inferReactiveScopeVariables(func); - inferMutableContextVariables(func); func.env.logger?.debugLogIRs?.({ kind: 'hir', name: 'AnalyseFunction (inner)', @@ -118,32 +77,16 @@ function lower(func: HIRFunction): void { }); } -function infer( - loweredFunc: LoweredFunction, - state: IdentifierState, - context: Array, -): void { - const mutations = new Map(); +function infer(loweredFunc: LoweredFunction): void { + const knownMutated = inferMutableContextVariables(loweredFunc.func); for (const operand of loweredFunc.func.context) { - if ( - isMutatedOrReassigned(operand.identifier) && - operand.identifier.name !== null - ) { - mutations.set(operand.identifier.name.value, operand.effect); - } - } - - for (const dep of loweredFunc.dependencies) { - let name: IdentifierName | null = null; - - if (state.properties.has(dep.identifier)) { - const receiver = state.properties.get(dep.identifier)!; - name = receiver.identifier.name; - } else { - name = dep.identifier.name; - } - - if (isRefOrRefValue(dep.identifier)) { + const identifier = operand.identifier; + CompilerError.invariant(operand.effect === Effect.Unknown, { + reason: + '[AnalyseFunctions] Expected Function context effects to not have been set', + loc: operand.loc, + }); + if (isRefOrRefValue(identifier)) { /* * TODO: this is a hack to ensure we treat functions which reference refs * as having a capture and therefore being considered mutable. this ensures @@ -151,43 +94,16 @@ function infer( * could be called, and allows us to help ensure it isn't called during * render */ - dep.effect = Effect.Capture; - } else if (name !== null) { - const effect = mutations.get(name.value); - if (effect !== undefined) { - dep.effect = effect === Effect.Unknown ? Effect.Capture : effect; - } - } - } - - /* - * This could potentially add duplicate deps to mutatedDeps in the case of - * mutating a context ref in the child function and in this parent function. - * It might be useful to dedupe this. - * - * In practice this never really matters because the Component function has no - * context refs, so it will never have duplicate deps. - */ - for (const place of context) { - CompilerError.invariant(place.identifier.name !== null, { - reason: 'context refs should always have a name', - description: null, - loc: place.loc, - suggestions: null, - }); - - const effect = mutations.get(place.identifier.name.value); - if (effect !== undefined) { - place.effect = effect === Effect.Unknown ? Effect.Capture : effect; - loweredFunc.dependencies.push(place); + operand.effect = Effect.Capture; + } else if (knownMutated.has(operand)) { + operand.effect = Effect.Mutate; + } else if (isMutatedOrReassigned(identifier)) { + // Note that this also reflects if identifier is ConditionallyMutated + operand.effect = Effect.Capture; + } else { + operand.effect = Effect.Read; } } - - for (const operand of loweredFunc.func.context) { - operand.identifier.mutableRange.start = makeInstructionId(0); - operand.identifier.mutableRange.end = makeInstructionId(0); - operand.identifier.scope = null; - } } function isMutatedOrReassigned(id: Identifier): boolean { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts index 67babf43db22f..0025472721542 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts @@ -55,32 +55,21 @@ import {IdentifierState} from './AnalyseFunctions'; * fn(); * ``` */ -export function inferMutableContextVariables(fn: HIRFunction): void { +export function inferMutableContextVariables(fn: HIRFunction): Set { const state = new IdentifierState(); const knownMutatedIdentifiers = new Set(); for (const [, block] of fn.body.blocks) { for (const instr of block.instructions) { switch (instr.value.kind) { - case 'PropertyLoad': { - state.declareProperty( - instr.lvalue, - instr.value.object, - instr.value.property, - ); - break; - } + case 'PropertyLoad': case 'ComputedLoad': { - /* - * The path is set to an empty string as the path doesn't really - * matter for a computed load. - */ - state.declareProperty(instr.lvalue, instr.value.object, ''); + state.alias(instr.lvalue.identifier, instr.value.object.identifier); break; } case 'LoadLocal': case 'LoadContext': { if (instr.lvalue.identifier.name === null) { - state.declareTemporary(instr.lvalue, instr.value.place); + state.alias(instr.lvalue.identifier, instr.value.place.identifier); } break; } @@ -95,11 +84,13 @@ export function inferMutableContextVariables(fn: HIRFunction): void { visitOperand(state, knownMutatedIdentifiers, operand); } } + const results = new Set(); for (const operand of fn.context) { if (knownMutatedIdentifiers.has(operand.identifier)) { - operand.effect = Effect.Mutate; + results.add(operand); } } + return results; } function visitOperand( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 0afe479df08dd..6420e74a2fd20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -390,29 +390,31 @@ class InferenceState { freezeValues(values: Set, reason: Set): void { for (const value of values) { + if (value.kind === 'DeclareContext') { + /** + * Avoid freezing hoisted context declarations + * function Component() { + * const cb = useBar(() => foo(2)); // produces a hoisted context declaration + * const foo = useFoo(); // reassigns to the context variable + * return ; + * } + */ + continue; + } this.#values.set(value, { kind: ValueKind.Frozen, reason, context: new Set(), }); - if (value.kind === 'FunctionExpression') { - if ( - this.#env.config.enablePreserveExistingMemoizationGuarantees || - this.#env.config.enableTransitivelyFreezeFunctionExpressions - ) { - if (value.kind === 'FunctionExpression') { - /* - * We want to freeze the captured values, not mark the operands - * themselves as frozen. There could be mutations that occur - * before the freeze we are processing, and it would be invalid - * to overwrite those mutations as a freeze. - */ - for (const operand of eachInstructionValueOperand(value)) { - const operandValues = this.#variables.get(operand.identifier.id); - if (operandValues !== undefined) { - this.freezeValues(operandValues, reason); - } - } + if ( + value.kind === 'FunctionExpression' && + (this.#env.config.enablePreserveExistingMemoizationGuarantees || + this.#env.config.enableTransitivelyFreezeFunctionExpressions) + ) { + for (const operand of value.loweredFunc.func.context) { + const operandValues = this.#variables.get(operand.identifier.id); + if (operandValues !== undefined) { + this.freezeValues(operandValues, reason); } } } @@ -1143,17 +1145,17 @@ function inferBlock( case 'ObjectMethod': case 'FunctionExpression': { let hasMutableOperand = false; - const mutableOperands: Array = []; for (const operand of eachInstructionOperand(instr)) { + CompilerError.invariant(operand.effect !== Effect.Unknown, { + reason: 'Expected fn effects to be populated', + loc: operand.loc, + }); state.referenceAndRecordEffects( freezeActions, operand, - operand.effect === Effect.Unknown ? Effect.Read : operand.effect, + operand.effect, ValueReason.Other, ); - if (isMutableEffect(operand.effect, operand.loc)) { - mutableOperands.push(operand); - } hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc); } /* diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts index e27b8f952148a..5b700b23b4049 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/LowerContextAccess.ts @@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array): Instruction { name: null, loweredFunc: { func: fn, - dependencies: [], }, type: 'ArrowFunctionExpression', loc: GeneratedSource, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts index 7a1473be40c87..0e6d1fd59201f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineFunctions.ts @@ -24,7 +24,6 @@ export function outlineFunctions( } if ( value.kind === 'FunctionExpression' && - value.loweredFunc.dependencies.length === 0 && value.loweredFunc.func.context.length === 0 && // TODO: handle outlining named functions value.loweredFunc.func.id === null && diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts index 1108422f070d7..1f104d8592fab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -379,6 +379,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); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts index bae038f9bd9df..12c8c0e2e6221 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EliminateRedundantPhi.ts @@ -13,6 +13,8 @@ import { eachTerminalOperand, } from '../HIR/visitors'; +const DEBUG = false; + /* * Pass to eliminate redundant phi nodes: * - all operands are the same identifier, ie `x2 = phi(x1, x1, x1)`. @@ -141,6 +143,23 @@ export function eliminateRedundantPhi( * have already propagated forwards since we visit in reverse postorder. */ } while (rewrites.size > size && hasBackEdge); + + if (DEBUG) { + for (const [, block] of ir.blocks) { + for (const phi of block.phis) { + CompilerError.invariant(!rewrites.has(phi.place.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + for (const [, operand] of phi.operands) { + CompilerError.invariant(!rewrites.has(operand.identifier), { + reason: '[EliminateRedundantPhis]: rewrite not complete', + loc: phi.place.loc, + }); + } + } + } + } } function rewritePlace( diff --git a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts index caba0d3c36992..820f7388dc41b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/SSA/EnterSSA.ts @@ -301,9 +301,6 @@ function enterSSAImpl( entry.preds.add(blockId); builder.defineFunction(loweredFunc); builder.enter(() => { - loweredFunc.context = loweredFunc.context.map(p => - builder.getPlace(p), - ); loweredFunc.params = loweredFunc.params.map(param => { if (param.kind === 'Identifier') { return builder.definePlace(param); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts index a35c4ddb0182c..a480b5d7c78de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Transform/TransformFire.ts @@ -319,51 +319,6 @@ function visitFunctionExpressionAndPropagateFireDependencies( replaceFireFunctions(fnExpr.loweredFunc.func, context), ); - /* - * Make a mapping from each dependency to the corresponding LoadLocal for it so that - * we can replace the loaded place with the generated fire function binding - */ - const loadLocalsToDepLoads = new Map(); - for (const dep of fnExpr.loweredFunc.dependencies) { - const loadLocal = context.getLoadLocalInstr(dep.identifier.id); - if (loadLocal != null) { - loadLocalsToDepLoads.set(loadLocal.place.identifier.id, loadLocal); - } - } - - const replacedCallees = new Map(); - for (const [ - calleeIdentifierId, - loadedFireFunctionBindingPlace, - ] of calleesCapturedByFnExpression.entries()) { - /* - * Given the ids of captured fire callees, look at the deps for loads of those identifiers - * and replace them with the new fire function binding - */ - const loadLocal = loadLocalsToDepLoads.get(calleeIdentifierId); - if (loadLocal == null) { - context.pushError({ - loc: fnExpr.loc, - description: null, - severity: ErrorSeverity.Invariant, - reason: - '[InsertFire] No loadLocal found for fire call argument for lambda', - suggestions: null, - }); - continue; - } - - const oldPlaceId = loadLocal.place.identifier.id; - loadLocal.place = { - ...loadedFireFunctionBindingPlace.fireFunctionBinding, - }; - - replacedCallees.set( - oldPlaceId, - loadedFireFunctionBindingPlace.fireFunctionBinding, - ); - } - // For each replaced callee, update the context of the function expression to track it for ( let contextIdx = 0; @@ -371,9 +326,13 @@ function visitFunctionExpressionAndPropagateFireDependencies( contextIdx++ ) { const contextItem = fnExpr.loweredFunc.func.context[contextIdx]; - const replacedCallee = replacedCallees.get(contextItem.identifier.id); + const replacedCallee = calleesCapturedByFnExpression.get( + contextItem.identifier.id, + ); if (replacedCallee != null) { - fnExpr.loweredFunc.func.context[contextIdx] = replacedCallee; + fnExpr.loweredFunc.func.context[contextIdx] = { + ...replacedCallee.fireFunctionBinding, + }; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.expect.md new file mode 100644 index 0000000000000..b30ee7e0d6817 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.expect.md @@ -0,0 +1,120 @@ + +## Input + +```javascript +// @enableTransitivelyFreezeFunctionExpressions:false +import { + Stringify, + mutate, + identity, + setPropertyByKey, + shallowCopy, +} from 'shared-runtime'; +/** + * Function expression version of `aliased-nested-scope-truncated-dep`. + * + * In this fixture, the output would be invalid if propagateScopeDeps did not + * avoid adding MemberExpression dependencies which would other evaluate during + * the mutable ranges of their base objects. + * This is different from `aliased-nested-scope-truncated-dep` which *does* + * produce correct output regardless of MemberExpression dependency truncation. + * + * Note while other expressions evaluate inline, function expressions *always* + * represent deferred evaluation. This means that + * (1) it's always safe to reorder function expression creation until its + * earliest potential invocation + * (2) it's invalid to eagerly evaluate function expression dependencies during + * their respective mutable ranges. + */ + +function Component({prop}) { + let obj = shallowCopy(prop); + + const aliasedObj = identity(obj); + + // When `obj` is mutable (either directly or through aliases), taking a + // dependency on `obj.id` is invalid as it may change before getId() is invoked + const getId = () => obj.id; + + mutate(aliasedObj); + setPropertyByKey(aliasedObj, 'id', prop.id + 1); + + // Calling getId() should return prop.id + 1, not the prev + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{prop: {id: 1}}], + sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enableTransitivelyFreezeFunctionExpressions:false +import { + Stringify, + mutate, + identity, + setPropertyByKey, + shallowCopy, +} from "shared-runtime"; +/** + * Function expression version of `aliased-nested-scope-truncated-dep`. + * + * In this fixture, the output would be invalid if propagateScopeDeps did not + * avoid adding MemberExpression dependencies which would other evaluate during + * the mutable ranges of their base objects. + * This is different from `aliased-nested-scope-truncated-dep` which *does* + * produce correct output regardless of MemberExpression dependency truncation. + * + * Note while other expressions evaluate inline, function expressions *always* + * represent deferred evaluation. This means that + * (1) it's always safe to reorder function expression creation until its + * earliest potential invocation + * (2) it's invalid to eagerly evaluate function expression dependencies during + * their respective mutable ranges. + */ + +function Component(t0) { + const $ = _c(2); + const { prop } = 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 = ; + $[0] = prop; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ prop: { id: 1 } }], + sequentialRenders: [ + { prop: { id: 1 } }, + { prop: { id: 1 } }, + { prop: { id: 2 } }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"getId":{"kind":"Function","result":2},"shouldInvokeFns":true}
+
{"getId":{"kind":"Function","result":2},"shouldInvokeFns":true}
+
{"getId":{"kind":"Function","result":3},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.tsx new file mode 100644 index 0000000000000..40022c6f651fd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.tsx @@ -0,0 +1,46 @@ +// @enableTransitivelyFreezeFunctionExpressions:false +import { + Stringify, + mutate, + identity, + setPropertyByKey, + shallowCopy, +} from 'shared-runtime'; +/** + * Function expression version of `aliased-nested-scope-truncated-dep`. + * + * In this fixture, the output would be invalid if propagateScopeDeps did not + * avoid adding MemberExpression dependencies which would other evaluate during + * the mutable ranges of their base objects. + * This is different from `aliased-nested-scope-truncated-dep` which *does* + * produce correct output regardless of MemberExpression dependency truncation. + * + * Note while other expressions evaluate inline, function expressions *always* + * represent deferred evaluation. This means that + * (1) it's always safe to reorder function expression creation until its + * earliest potential invocation + * (2) it's invalid to eagerly evaluate function expression dependencies during + * their respective mutable ranges. + */ + +function Component({prop}) { + let obj = shallowCopy(prop); + + const aliasedObj = identity(obj); + + // When `obj` is mutable (either directly or through aliases), taking a + // dependency on `obj.id` is invalid as it may change before getId() is invoked + const getId = () => obj.id; + + mutate(aliasedObj); + setPropertyByKey(aliasedObj, 'id', prop.id + 1); + + // Calling getId() should return prop.id + 1, not the prev + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{prop: {id: 1}}], + sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.expect.md new file mode 100644 index 0000000000000..933fafff5f1ba --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.expect.md @@ -0,0 +1,221 @@ + +## Input + +```javascript +import { + Stringify, + mutate, + identity, + shallowCopy, + setPropertyByKey, +} from 'shared-runtime'; + +/** + * This fixture is similar to `bug-aliased-capture-aliased-mutate` and + * `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on + * dependency extraction. + * + * NOTE: this fixture is currently valid, but will break with optimizations: + * - Scope and mutable-range based reordering may move the array creation + * *after* the `mutate(aliasedObj)` call. This is invalid if mutate + * reassigns inner properties. + * - RecycleInto or other deeper-equality optimizations may produce invalid + * output -- it may compare the array's contents / dependencies too early. + * - Runtime validation for immutable values will break if `mutate` does + * interior mutation of the value captured into the array. + * + * Before scope block creation, HIR looks like this: + * // + * // $1 is unscoped as obj's mutable range will be + * // extended in a later pass + * // + * $1 = LoadLocal obj@0[0:12] + * $2 = PropertyLoad $1.id + * // + * // $3 gets assigned a scope as Array is an allocating + * // instruction, but this does *not* get extended or + * // merged into the later mutation site. + * // (explained in `bug-aliased-capture-aliased-mutate`) + * // + * $3@1 = Array[$2] + * ... + * $10@0 = LoadLocal shallowCopy@0[0, 12] + * $11 = LoadGlobal mutate + * $12 = $11($10@0[0, 12]) + * + * When filling in scope dependencies, we find that it's incorrect to depend on + * PropertyLoads from obj as it hasn't completed its mutable range. Following + * the immutable / mutable-new typing system, we check the identity of obj to + * detect whether it was newly created (and thus mutable) in this render pass. + * + * HIR with scopes looks like this. + * bb0: + * $1 = LoadLocal obj@0[0:12] + * $2 = PropertyLoad $1.id + * scopeTerminal deps=[obj@0] block=bb1 fallt=bb2 + * bb1: + * $3@1 = Array[$2] + * goto bb2 + * bb2: + * ... + * + * This is surprising as deps now is entirely decoupled from temporaries used + * by the block itself. scope @1's instructions now reference a value (1) + * produced outside its scope range and (2) not represented in its dependencies + * + * The right thing to do is to ensure that all Loads from a value get assigned + * the value's reactive scope. This also requires track mutating and aliasing + * separately from scope range. In this example, that would correctly merge + * the scopes of $3 with obj. + * Runtime validation and optimizations such as ReactiveGraph-based reordering + * require this as well. + * + * A tempting fix is to instead extend $3's ReactiveScope range up to include + * $2 (the PropertyLoad). This fixes dependency deduping but not reordering + * and mutability. + */ +function Component({prop}) { + let obj = shallowCopy(prop); + const aliasedObj = identity(obj); + + // [obj.id] currently is assigned its own reactive scope + const id = [obj.id]; + + // Writing to the alias may reassign to previously captured references. + // The compiler currently produces valid output, but this breaks with + // reordering, recycleInto, and other potential optimizations. + mutate(aliasedObj); + setPropertyByKey(aliasedObj, 'id', prop.id + 1); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{prop: {id: 1}}], + sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { + Stringify, + mutate, + identity, + shallowCopy, + setPropertyByKey, +} from "shared-runtime"; + +/** + * This fixture is similar to `bug-aliased-capture-aliased-mutate` and + * `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on + * dependency extraction. + * + * NOTE: this fixture is currently valid, but will break with optimizations: + * - Scope and mutable-range based reordering may move the array creation + * *after* the `mutate(aliasedObj)` call. This is invalid if mutate + * reassigns inner properties. + * - RecycleInto or other deeper-equality optimizations may produce invalid + * output -- it may compare the array's contents / dependencies too early. + * - Runtime validation for immutable values will break if `mutate` does + * interior mutation of the value captured into the array. + * + * Before scope block creation, HIR looks like this: + * // + * // $1 is unscoped as obj's mutable range will be + * // extended in a later pass + * // + * $1 = LoadLocal obj@0[0:12] + * $2 = PropertyLoad $1.id + * // + * // $3 gets assigned a scope as Array is an allocating + * // instruction, but this does *not* get extended or + * // merged into the later mutation site. + * // (explained in `bug-aliased-capture-aliased-mutate`) + * // + * $3@1 = Array[$2] + * ... + * $10@0 = LoadLocal shallowCopy@0[0, 12] + * $11 = LoadGlobal mutate + * $12 = $11($10@0[0, 12]) + * + * When filling in scope dependencies, we find that it's incorrect to depend on + * PropertyLoads from obj as it hasn't completed its mutable range. Following + * the immutable / mutable-new typing system, we check the identity of obj to + * detect whether it was newly created (and thus mutable) in this render pass. + * + * HIR with scopes looks like this. + * bb0: + * $1 = LoadLocal obj@0[0:12] + * $2 = PropertyLoad $1.id + * scopeTerminal deps=[obj@0] block=bb1 fallt=bb2 + * bb1: + * $3@1 = Array[$2] + * goto bb2 + * bb2: + * ... + * + * This is surprising as deps now is entirely decoupled from temporaries used + * by the block itself. scope @1's instructions now reference a value (1) + * produced outside its scope range and (2) not represented in its dependencies + * + * The right thing to do is to ensure that all Loads from a value get assigned + * the value's reactive scope. This also requires track mutating and aliasing + * separately from scope range. In this example, that would correctly merge + * the scopes of $3 with obj. + * Runtime validation and optimizations such as ReactiveGraph-based reordering + * require this as well. + * + * A tempting fix is to instead extend $3's ReactiveScope range up to include + * $2 (the PropertyLoad). This fixes dependency deduping but not reordering + * and mutability. + */ +function Component(t0) { + const $ = _c(4); + const { prop } = t0; + let t1; + if ($[0] !== prop) { + const obj = shallowCopy(prop); + const aliasedObj = identity(obj); + let t2; + if ($[2] !== obj) { + t2 = [obj.id]; + $[2] = obj; + $[3] = t2; + } else { + t2 = $[3]; + } + const id = t2; + + mutate(aliasedObj); + setPropertyByKey(aliasedObj, "id", prop.id + 1); + + t1 = ; + $[0] = prop; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ prop: { id: 1 } }], + sequentialRenders: [ + { prop: { id: 1 } }, + { prop: { id: 1 } }, + { prop: { id: 2 } }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"id":[1]}
+
{"id":[1]}
+
{"id":[2]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.tsx new file mode 100644 index 0000000000000..4d9d7e78fb309 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.tsx @@ -0,0 +1,93 @@ +import { + Stringify, + mutate, + identity, + shallowCopy, + setPropertyByKey, +} from 'shared-runtime'; + +/** + * This fixture is similar to `bug-aliased-capture-aliased-mutate` and + * `nonmutating-capture-in-unsplittable-memo-block`, but with a focus on + * dependency extraction. + * + * NOTE: this fixture is currently valid, but will break with optimizations: + * - Scope and mutable-range based reordering may move the array creation + * *after* the `mutate(aliasedObj)` call. This is invalid if mutate + * reassigns inner properties. + * - RecycleInto or other deeper-equality optimizations may produce invalid + * output -- it may compare the array's contents / dependencies too early. + * - Runtime validation for immutable values will break if `mutate` does + * interior mutation of the value captured into the array. + * + * Before scope block creation, HIR looks like this: + * // + * // $1 is unscoped as obj's mutable range will be + * // extended in a later pass + * // + * $1 = LoadLocal obj@0[0:12] + * $2 = PropertyLoad $1.id + * // + * // $3 gets assigned a scope as Array is an allocating + * // instruction, but this does *not* get extended or + * // merged into the later mutation site. + * // (explained in `bug-aliased-capture-aliased-mutate`) + * // + * $3@1 = Array[$2] + * ... + * $10@0 = LoadLocal shallowCopy@0[0, 12] + * $11 = LoadGlobal mutate + * $12 = $11($10@0[0, 12]) + * + * When filling in scope dependencies, we find that it's incorrect to depend on + * PropertyLoads from obj as it hasn't completed its mutable range. Following + * the immutable / mutable-new typing system, we check the identity of obj to + * detect whether it was newly created (and thus mutable) in this render pass. + * + * HIR with scopes looks like this. + * bb0: + * $1 = LoadLocal obj@0[0:12] + * $2 = PropertyLoad $1.id + * scopeTerminal deps=[obj@0] block=bb1 fallt=bb2 + * bb1: + * $3@1 = Array[$2] + * goto bb2 + * bb2: + * ... + * + * This is surprising as deps now is entirely decoupled from temporaries used + * by the block itself. scope @1's instructions now reference a value (1) + * produced outside its scope range and (2) not represented in its dependencies + * + * The right thing to do is to ensure that all Loads from a value get assigned + * the value's reactive scope. This also requires track mutating and aliasing + * separately from scope range. In this example, that would correctly merge + * the scopes of $3 with obj. + * Runtime validation and optimizations such as ReactiveGraph-based reordering + * require this as well. + * + * A tempting fix is to instead extend $3's ReactiveScope range up to include + * $2 (the PropertyLoad). This fixes dependency deduping but not reordering + * and mutability. + */ +function Component({prop}) { + let obj = shallowCopy(prop); + const aliasedObj = identity(obj); + + // [obj.id] currently is assigned its own reactive scope + const id = [obj.id]; + + // Writing to the alias may reassign to previously captured references. + // The compiler currently produces valid output, but this breaks with + // reordering, recycleInto, and other potential optimizations. + mutate(aliasedObj); + setPropertyByKey(aliasedObj, 'id', prop.id + 1); + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{prop: {id: 1}}], + sequentialRenders: [{prop: {id: 1}}, {prop: {id: 1}}, {prop: {id: 2}}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md index 37a510b8c290a..3584faf699f86 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-ref-access-in-unused-callback-nested.expect.md @@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen import { useEffect, useRef, useState } from "react"; function Component() { - const $ = _c(6); + const $ = _c(5); const ref = useRef(null); const [state, setState] = useState(false); let t0; - let t1; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => {}; - - t1 = []; + t0 = []; $[0] = t0; - $[1] = t1; } else { t0 = $[0]; - t1 = $[1]; } - useEffect(t0, t1); + useEffect(_temp, t0); + let t1; let t2; - let t3; - if ($[2] === Symbol.for("react.memo_cache_sentinel")) { - t2 = () => { + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { setState(true); }; - t3 = []; + t2 = []; + $[1] = t1; $[2] = t2; - $[3] = t3; } else { + t1 = $[1]; t2 = $[2]; - t3 = $[3]; } - useEffect(t2, t3); + useEffect(t1, t2); - const t4 = String(state); - let t5; - if ($[4] !== t4) { - t5 = ; + const t3 = String(state); + let t4; + if ($[3] !== t3) { + t4 = ; + $[3] = t3; $[4] = t4; - $[5] = t5; } else { - t5 = $[5]; + t4 = $[4]; } - return t5; + return t4; } +function _temp() {} function Child(t0) { const { ref } = t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md index c071d5d20ed99..6836544c5d337 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-mutate-2.expect.md @@ -27,7 +27,6 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; function component(a, b) { const $ = _c(2); - const y = { b }; let z; if ($[0] !== a) { z = { a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md index aa32b3260ef50..14bf94e770a6b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-no-mutate.expect.md @@ -31,12 +31,20 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; function Component(t0) { - const $ = _c(3); + const $ = _c(5); const { a, b } = t0; let z; if ($[0] !== a || $[1] !== b) { z = { a }; - const y = { b }; + let t1; + if ($[3] !== b) { + t1 = { b }; + $[3] = b; + $[4] = t1; + } else { + t1 = $[4]; + } + const y = t1; const x = function () { z.a = 2; return Math.max(y.b, 0); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md index 1b91bc1a11275..a071dddba6b4c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-func-simple-alias-iife.expect.md @@ -34,7 +34,6 @@ function component(a) { const x = { a }; y = {}; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md index f4721a507f31f..2afc5fd25dbac 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-2-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0][1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md index 5c0be290a6930..3e57b7dc7c255 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-3-iife.expect.md @@ -37,8 +37,6 @@ function bar(a, b) { let t; t = {}; - y; - t; y = x[0][1]; t = x[1][0]; $[0] = a; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md index 34b927d91e5f7..22728aaf4323d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-4-iife.expect.md @@ -31,7 +31,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0].a[1]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md index 0978be54acb46..60f829cdc4d66 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-function-alias-computed-load-iife.expect.md @@ -30,7 +30,6 @@ function bar(a) { const x = [a]; y = {}; - y; y = x[0]; $[0] = a; $[1] = y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md index 1bdc1c09a3483..299aa5a31dff2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/capturing-reference-changes-type.expect.md @@ -25,7 +25,6 @@ function component(a) { const x = { a }; y = 1; - y; y = x; mutate(y); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md index d17c934b3b42c..cf85967682607 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-inline-iife-reassign.expect.md @@ -38,9 +38,8 @@ function useTest() { const t1 = (w = 42); const t2 = w; - - w; let t3; + w = 999; t3 = 2; t0 = makeArray(t1, t2, t3); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md index e42ea8ce933b8..04b6c4f17f41a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-global.expect.md @@ -19,10 +19,10 @@ function foo() { import { c as _c } from "react/compiler-runtime"; function foo() { const $ = _c(1); + + const getJSX = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const getJSX = () => ; - t0 = getJSX(); $[0] = t0; } else { @@ -31,6 +31,9 @@ function foo() { const result = t0; return result; } +function _temp() { + return ; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md index 6686c0b5301bf..60fe0808d922a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/const-propagation-into-function-expression-primitive.expect.md @@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo() { - const f = () => { - console.log(42); - }; + const f = _temp; f(); return 42; } +function _temp() { + console.log(42); +} export const FIXTURE_ENTRYPOINT = { fn: foo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md index 8ea2190480003..8822eddcdb69f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-into-function-expressions.expect.md @@ -18,12 +18,10 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(1); + + const onEvent = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const onEvent = () => { - console.log(42); - }; - t0 = ; $[0] = t0; } else { @@ -31,6 +29,9 @@ function Component(props) { } return t0; } +function _temp() { + console.log(42); +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 3dc0dba27c364..da3bb94ed5ed4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -34,9 +34,8 @@ function Component(props) { let Component; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { Component = Stringify; - - Component; let t0; + t0 = Component; Component = t0; $[0] = Component; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 2045ee7901e96..1ba0d59e17265 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` + 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting - 6 | function baz() { +> 6 | function baz() { + | ^^^^^^^^^^^^^^^^ > 7 | return bar(); - | ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7) - 8 | } + | ^^^^^^^^^^^^^^^^^ +> 8 | } + | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) 9 | } 10 | + 11 | export const FIXTURE_ENTRYPOINT = { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md new file mode 100644 index 0000000000000..7babe57b000e2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md @@ -0,0 +1,41 @@ + +## Input + +```javascript +// @enableTransitivelyFreezeFunctionExpressions +import {setPropertyByKey, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({count}) { + const x = {value: 0}; + /** + * After this custom hook call, it's no longer valid to mutate x. + */ + const cb = useIdentity(() => { + setPropertyByKey(x, 'value', count); + }); + + x.value += count; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{count: 1}], +}; + +``` + + +## Error + +``` + 11 | }); + 12 | +> 13 | x.value += count; + | ^ InvalidReact: This mutates a variable that React considers immutable (13:13) + 14 | return ; + 15 | } + 16 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.tsx new file mode 100644 index 0000000000000..b71626a435b78 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.tsx @@ -0,0 +1,20 @@ +// @enableTransitivelyFreezeFunctionExpressions +import {setPropertyByKey, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({count}) { + const x = {value: 0}; + /** + * After this custom hook call, it's no longer valid to mutate x. + */ + const cb = useIdentity(() => { + setPropertyByKey(x, 'value', count); + }); + + x.value += count; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{count: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md new file mode 100644 index 0000000000000..fcc47ddc2b14f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md @@ -0,0 +1,41 @@ + +## Input + +```javascript +// @enableTransitivelyFreezeFunctionExpressions +import {mutate, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({count}) { + const x = {value: 0}; + /** + * After this custom hook call, it's no longer valid to mutate x. + */ + const cb = useIdentity(() => { + x.value++; + }); + + x.value += count; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{count: 1}], +}; + +``` + + +## Error + +``` + 11 | }); + 12 | +> 13 | x.value += count; + | ^ InvalidReact: This mutates a variable that React considers immutable (13:13) + 14 | return ; + 15 | } + 16 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.jsx new file mode 100644 index 0000000000000..2a94559c1f026 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.jsx @@ -0,0 +1,20 @@ +// @enableTransitivelyFreezeFunctionExpressions +import {mutate, Stringify, useIdentity} from 'shared-runtime'; + +function Foo({count}) { + const x = {value: 0}; + /** + * After this custom hook call, it's no longer valid to mutate x. + */ + const cb = useIdentity(() => { + x.value++; + }); + + x.value += count; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{count: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md index db3a192eaf604..f66b970f00dd4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-named-function-with-shadowed-local-same-name.expect.md @@ -22,7 +22,7 @@ function Component(props) { 7 | return hasErrors; 8 | } > 9 | return hasErrors(); - | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9) + | ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9) 10 | } 11 | ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.expect.md new file mode 100644 index 0000000000000..55cab1e9f3bd1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.expect.md @@ -0,0 +1,81 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +function useFoo() { + const onClick = response => { + setState(DISABLED_FORM); + }; + + const [state, setState] = useState(); + const handleLogout = useCallback(() => { + setState(DISABLED_FORM); + }, [setState]); + const getComponent = () => { + return handleLogout()} />; + }; + + // this `getComponent` call should not be inferred as mutating setState + return [getComponent(), onClick]; // pass onClick to avoid dce +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +function useFoo() { + const $ = _c(9); + const onClick = (response) => { + setState(DISABLED_FORM); + }; + + const [, t0] = useState(); + const setState = t0; + let t1; + if ($[0] !== setState) { + t1 = () => { + setState(DISABLED_FORM); + }; + $[0] = setState; + $[1] = t1; + } else { + t1 = $[1]; + } + setState; + const handleLogout = t1; + let t2; + if ($[2] !== handleLogout) { + t2 = () => handleLogout()} />; + $[2] = handleLogout; + $[3] = t2; + } else { + t2 = $[3]; + } + const getComponent = t2; + let t3; + if ($[4] !== getComponent) { + t3 = getComponent(); + $[4] = getComponent; + $[5] = t3; + } else { + t3 = $[5]; + } + let t4; + if ($[6] !== onClick || $[7] !== t3) { + t4 = [t3, onClick]; + $[6] = onClick; + $[7] = t3; + $[8] = t4; + } else { + t4 = $[8]; + } + return t4; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.js new file mode 100644 index 0000000000000..4a44679390a80 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate-captured-indirectly-jsx.js @@ -0,0 +1,17 @@ +// @validatePreserveExistingMemoizationGuarantees +function useFoo() { + const onClick = response => { + setState(DISABLED_FORM); + }; + + const [state, setState] = useState(); + const handleLogout = useCallback(() => { + setState(DISABLED_FORM); + }, [setState]); + const getComponent = () => { + return handleLogout()} />; + }; + + // this `getComponent` call should not be inferred as mutating setState + return [getComponent(), onClick]; // pass onClick to avoid dce +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md new file mode 100644 index 0000000000000..483d9b1a8e2da --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.expect.md @@ -0,0 +1,77 @@ + +## Input + +```javascript +import {useEffect, useState} from 'react'; +import {Stringify} from 'shared-runtime'; + +function Foo() { + /** + * Previously, this lowered to + * $1 = LoadContext capture setState + * $2 = FunctionExpression deps=$1 context=setState + * [[ at this point, we freeze the `LoadContext setState` instruction, but it will never be referenced again ]] + * + * Now, this function expression directly references `setState`, which freezes + * the source `DeclareContext HoistedConst setState`. Freezing source identifiers + * (instead of the one level removed `LoadContext`) is more semantically correct + * for everything *other* than hoisted context declarations. + * + * $2 = Function context=setState + */ + useEffect(() => setState(2), []); + + const [state, setState] = useState(0); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useEffect, useState } from "react"; +import { Stringify } from "shared-runtime"; + +function Foo() { + const $ = _c(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = []; + $[0] = t0; + } else { + t0 = $[0]; + } + useEffect(() => setState(2), t0); + + const [state, t1] = useState(0); + const setState = t1; + let t2; + if ($[1] !== state) { + t2 = ; + $[1] = state; + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +### Eval output +(kind: ok)
{"state":2}
+
{"state":2}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js new file mode 100644 index 0000000000000..7b26c8d086491 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-setstate.js @@ -0,0 +1,28 @@ +import {useEffect, useState} from 'react'; +import {Stringify} from 'shared-runtime'; + +function Foo() { + /** + * Previously, this lowered to + * $1 = LoadContext capture setState + * $2 = FunctionExpression deps=$1 context=setState + * [[ at this point, we freeze the `LoadContext setState` instruction, but it will never be referenced again ]] + * + * Now, this function expression directly references `setState`, which freezes + * the source `DeclareContext HoistedConst setState`. Freezing source identifiers + * (instead of the one level removed `LoadContext`) is more semantically correct + * for everything *other* than hoisted context declarations. + * + * $2 = Function context=setState + */ + useEffect(() => setState(2), []); + + const [state, setState] = useState(0); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{}], + sequentialRenders: [{}, {}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.expect.md new file mode 100644 index 0000000000000..957919516d09d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.expect.md @@ -0,0 +1,87 @@ + +## Input + +```javascript +import {useIdentity, Stringify, identity} from 'shared-runtime'; + +function Foo({val1}) { + // `x={inner: val1}` should be able to be memoized + const x = {inner: val1}; + + // Any references to `x` after this hook call should be read-only + const cb = useIdentity(() => x.inner); + + // With enableTransitivelyFreezeFunctionExpressions, it's invalid + // to write to `x` after it's been frozen. + // TODO: runtime validation for DX + const copy = identity(x); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{val1: 1}], + sequentialRenders: [{val1: 1}, {val1: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useIdentity, Stringify, identity } from "shared-runtime"; + +function Foo(t0) { + const $ = _c(9); + const { val1 } = t0; + let t1; + if ($[0] !== val1) { + t1 = { inner: val1 }; + $[0] = val1; + $[1] = t1; + } else { + t1 = $[1]; + } + const x = t1; + let t2; + if ($[2] !== x.inner) { + t2 = () => x.inner; + $[2] = x.inner; + $[3] = t2; + } else { + t2 = $[3]; + } + const cb = useIdentity(t2); + let t3; + if ($[4] !== x) { + t3 = identity(x); + $[4] = x; + $[5] = t3; + } else { + t3 = $[5]; + } + const copy = t3; + let t4; + if ($[6] !== cb || $[7] !== copy) { + t4 = ; + $[6] = cb; + $[7] = copy; + $[8] = t4; + } else { + t4 = $[8]; + } + return t4; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ val1: 1 }], + sequentialRenders: [{ val1: 1 }, { val1: 1 }], +}; + +``` + +### Eval output +(kind: ok)
{"copy":{"inner":1},"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
+
{"copy":{"inner":1},"cb":{"kind":"Function","result":1},"shouldInvokeFns":true}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.tsx new file mode 100644 index 0000000000000..68e8e034379e1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-call-freezes-captured-memberexpr.tsx @@ -0,0 +1,21 @@ +import {useIdentity, Stringify, identity} from 'shared-runtime'; + +function Foo({val1}) { + // `x={inner: val1}` should be able to be memoized + const x = {inner: val1}; + + // Any references to `x` after this hook call should be read-only + const cb = useIdentity(() => x.inner); + + // With enableTransitivelyFreezeFunctionExpressions, it's invalid + // to write to `x` after it's been frozen. + // TODO: runtime validation for DX + const copy = identity(x); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{val1: 1}], + sequentialRenders: [{val1: 1}, {val1: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md index 74e01a72d52ba..a7d27bc38193f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-local-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import { Stringify } from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md index 22fa3b2e2a2e3..e5ead2479dd40 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-memberexpr-tag-in-lambda.expect.md @@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime"; import * as SharedRuntime from "shared-runtime"; function useFoo() { const $ = _c(1); + + const callback = _temp; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - const callback = () => ; - t0 = callback(); $[0] = t0; } else { @@ -36,6 +36,9 @@ function useFoo() { } return t0; } +function _temp() { + return ; +} export const FIXTURE_ENTRYPOINT = { fn: useFoo, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md index d34db46d6aa28..ed0ddda55b32f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-non-reactive-to-reactive.expect.md @@ -26,7 +26,6 @@ function f(a) { const $ = _c(4); let x; if ($[0] !== a) { - x; x = { a }; $[0] = a; $[1] = x; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md index 2aa5d4d06dfb6..8dc4839085ee5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/lambda-mutated-ref-non-reactive.expect.md @@ -27,7 +27,6 @@ function f(a) { const $ = _c(2); let x; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - x; x = {}; $[0] = x; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md index 13ba6d17986bb..3c624de9ebe57 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nested-function-shadowed-identifiers.expect.md @@ -31,7 +31,7 @@ function Component(props) { let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => { - setX((currentX) => currentX + null); + setX(_temp); }; $[0] = t0; } else { @@ -48,6 +48,9 @@ function Component(props) { } return t1; } +function _temp(currentX) { + return currentX + null; +} export const FIXTURE_ENTRYPOINT = { fn: Component, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index e8a3e2d627c59..3fffec6a7dc20 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -35,7 +35,6 @@ function useFoo(arr1, arr2) { if ($[0] !== arr1 || $[1] !== arr2) { const x = [arr1]; - y; (y = x.concat(arr2)), y; $[0] = arr1; $[1] = arr2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md index 2e451d8948988..0c66dee6a85b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rewrite-phis-in-lambda-capture-context.expect.md @@ -22,26 +22,21 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; function Component() { - const $ = _c(1); - let t0; - if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { - while (bar()) { - if (baz) { - bar(); - } - } - return () => 4; - }; - $[0] = t0; - } else { - t0 = $[0]; - } - const get4 = t0; + const get4 = _temp2; return get4; } +function _temp2() { + while (bar()) { + if (baz) { + bar(); + } + } + return _temp; +} +function _temp() { + return 4; +} ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md index e335273026791..6ad460347fa50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-operator-conditional.expect.md @@ -85,7 +85,6 @@ function Inner(props) { input = use(FooContext); } - input; input; let t0; let t1; From a92acdb188990b2b64130d74ccd6437dc9db1901 Mon Sep 17 00:00:00 2001 From: mofeiZ <34200447+mofeiZ@users.noreply.github.com> Date: Tue, 18 Feb 2025 09:37:21 -0700 Subject: [PATCH 38/73] [compiler] Remove redundant InferMutableContextVariables (#32097) This removes special casing for `PropertyStore` mutability inference within FunctionExpressions. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32097). * #32287 * #32104 * #32098 * __->__ #32097 --- .../src/Inference/AnalyseFunctions.ts | 27 +---- .../Inference/InferMutableContextVariables.ts | 105 ------------------ ...mutate-global-in-effect-fixpoint.expect.md | 66 ++++++----- .../allow-mutate-global-in-effect-fixpoint.js | 7 +- .../reanimated-shared-value-writes.expect.md | 23 ++-- 5 files changed, 54 insertions(+), 174 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts index 5381262874369..a439b4cd01232 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AnalyseFunctions.ts @@ -10,7 +10,6 @@ import { Effect, HIRFunction, Identifier, - IdentifierId, LoweredFunction, isRefOrRefValue, makeInstructionId, @@ -18,27 +17,9 @@ import { import {deadCodeElimination} from '../Optimization'; import {inferReactiveScopeVariables} from '../ReactiveScopes'; import {rewriteInstructionKindsBasedOnReassignment} from '../SSA'; -import {inferMutableContextVariables} from './InferMutableContextVariables'; import {inferMutableRanges} from './InferMutableRanges'; import inferReferenceEffects from './InferReferenceEffects'; -// Helper class to track indirections such as LoadLocal and PropertyLoad. -export class IdentifierState { - properties: Map = new Map(); - - resolve(identifier: Identifier): Identifier { - const resolved = this.properties.get(identifier.id); - if (resolved !== undefined) { - return resolved; - } - return identifier; - } - - alias(lvalue: Identifier, value: Identifier): void { - this.properties.set(lvalue.id, this.properties.get(value.id) ?? value); - } -} - export default function analyseFunctions(func: HIRFunction): void { for (const [_, block] of func.body.blocks) { for (const instr of block.instructions) { @@ -78,7 +59,6 @@ function lower(func: HIRFunction): void { } function infer(loweredFunc: LoweredFunction): void { - const knownMutated = inferMutableContextVariables(loweredFunc.func); for (const operand of loweredFunc.func.context) { const identifier = operand.identifier; CompilerError.invariant(operand.effect === Effect.Unknown, { @@ -95,10 +75,11 @@ function infer(loweredFunc: LoweredFunction): void { * render */ operand.effect = Effect.Capture; - } else if (knownMutated.has(operand)) { - operand.effect = Effect.Mutate; } else if (isMutatedOrReassigned(identifier)) { - // Note that this also reflects if identifier is ConditionallyMutated + /** + * Reflects direct reassignments, PropertyStores, and ConditionallyMutate + * (directly or through maybe-aliases) + */ operand.effect = Effect.Capture; } else { operand.effect = Effect.Read; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts deleted file mode 100644 index 0025472721542..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableContextVariables.ts +++ /dev/null @@ -1,105 +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 {Effect, HIRFunction, Identifier, Place} from '../HIR'; -import { - eachInstructionValueOperand, - eachTerminalOperand, -} from '../HIR/visitors'; -import {IdentifierState} from './AnalyseFunctions'; - -/* - * This pass infers which of the given function's context (free) variables - * are definitively mutated by the function. This analysis is *partial*, - * and only annotates provable mutations, and may miss mutations via indirections. - * The intent of this pass is to drive validations, rejecting known-bad code - * while avoiding false negatives, and the inference should *not* be used to - * drive changes in output. - * - * Note that a complete analysis is possible but would have too many false negatives. - * The approach would be to run LeaveSSA and InferReactiveScopeVariables in order to - * find all possible aliases of a context variable which may be mutated. However, this - * can lead to false negatives: - * - * ``` - * const [x, setX] = useState(null); // x is frozen - * const fn = () => { // context=[x] - * const z = {}; // z is mutable - * foo(z, x); // potentially mutate z and x - * z.a = true; // definitively mutate z - * } - * fn(); - * ``` - * - * When we analyze function expressions we assume that context variables are mutable, - * so we assume that `x` is mutable. We infer that `foo(z, x)` could be mutating the - * two variables to alias each other, such that `z.a = true` could be mutating `x`, - * and we would infer that `x` is definitively mutated. Then when we run - * InferReferenceEffects on the outer code we'd reject it, since there is a definitive - * mutation of a frozen value. - * - * Thus the actual implementation looks at only basic aliasing. The above example would - * pass, since z does not directly alias `x`. However, mutations through trivial aliases - * are detected: - * - * ``` - * const [x, setX] = useState(null); // x is frozen - * const fn = () => { // context=[x] - * const z = x; - * z.a = true; // ERROR: mutates x - * } - * fn(); - * ``` - */ -export function inferMutableContextVariables(fn: HIRFunction): Set { - const state = new IdentifierState(); - const knownMutatedIdentifiers = new Set(); - for (const [, block] of fn.body.blocks) { - for (const instr of block.instructions) { - switch (instr.value.kind) { - case 'PropertyLoad': - case 'ComputedLoad': { - state.alias(instr.lvalue.identifier, instr.value.object.identifier); - break; - } - case 'LoadLocal': - case 'LoadContext': { - if (instr.lvalue.identifier.name === null) { - state.alias(instr.lvalue.identifier, instr.value.place.identifier); - } - break; - } - default: { - for (const operand of eachInstructionValueOperand(instr.value)) { - visitOperand(state, knownMutatedIdentifiers, operand); - } - } - } - } - for (const operand of eachTerminalOperand(block.terminal)) { - visitOperand(state, knownMutatedIdentifiers, operand); - } - } - const results = new Set(); - for (const operand of fn.context) { - if (knownMutatedIdentifiers.has(operand.identifier)) { - results.add(operand); - } - } - return results; -} - -function visitOperand( - state: IdentifierState, - knownMutatedIdentifiers: Set, - operand: Place, -): void { - const resolved = state.resolve(operand.identifier); - if (operand.effect === Effect.Mutate || operand.effect === Effect.Store) { - knownMutatedIdentifiers.add(resolved); - } -} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md index 942daec1dd08c..76e4432fe90d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.expect.md @@ -19,9 +19,14 @@ function Component() { // capture into a separate variable that is not a context variable. const y = x; + /** + * Note that this fixture currently produces a stale effect closure if `y = x + * = someGlobal` changes between renders. Under current compiler assumptions, + * that would be a rule of react violation. + */ useEffect(() => { y.value = 'hello'; - }, []); + }); useEffect(() => { setState(someGlobal.value); @@ -46,57 +51,50 @@ import { useEffect, useState } from "react"; let someGlobal = { value: null }; function Component() { - const $ = _c(7); + const $ = _c(5); const [state, setState] = useState(someGlobal); + + let x = someGlobal; + while (x == null) { + x = someGlobal; + } + + const y = x; let t0; - let t1; - let t2; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - let x = someGlobal; - while (x == null) { - x = someGlobal; - } - - const y = x; - t0 = useEffect; - t1 = () => { + t0 = () => { y.value = "hello"; }; - t2 = []; $[0] = t0; + } else { + t0 = $[0]; + } + useEffect(t0); + let t1; + let t2; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + setState(someGlobal.value); + }; + t2 = [someGlobal]; $[1] = t1; $[2] = t2; } else { - t0 = $[0]; t1 = $[1]; t2 = $[2]; } - t0(t1, t2); - let t3; + useEffect(t1, t2); + + const t3 = String(state); let t4; - if ($[3] === Symbol.for("react.memo_cache_sentinel")) { - t3 = () => { - setState(someGlobal.value); - }; - t4 = [someGlobal]; + if ($[3] !== t3) { + t4 =
{t3}
; $[3] = t3; $[4] = t4; } else { - t3 = $[3]; t4 = $[4]; } - useEffect(t3, t4); - - const t5 = String(state); - let t6; - if ($[5] !== t5) { - t6 =
{t5}
; - $[5] = t5; - $[6] = t6; - } else { - t6 = $[6]; - } - return t6; + return t4; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js index 84bd42aaefd18..6e44adf204101 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-mutate-global-in-effect-fixpoint.js @@ -15,9 +15,14 @@ function Component() { // capture into a separate variable that is not a context variable. const y = x; + /** + * Note that this fixture currently produces a stale effect closure if `y = x + * = someGlobal` changes between renders. Under current compiler assumptions, + * that would be a rule of react violation. + */ useEffect(() => { y.value = 'hello'; - }, []); + }); useEffect(() => { setState(someGlobal.value); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md index 8f808c94b3043..0a19a85428939 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reanimated-shared-value-writes.expect.md @@ -36,21 +36,22 @@ import { useSharedValue } from "react-native-reanimated"; * of render */ function SomeComponent() { - const $ = _c(3); + const $ = _c(2); const sharedVal = useSharedValue(0); - - const T0 = Button; - const t0 = () => (sharedVal.value = Math.random()); - let t1; - if ($[0] !== T0 || $[1] !== t0) { - t1 = ; - $[0] = T0; + let t0; + if ($[0] !== sharedVal) { + t0 = ( +
diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index ddf051bcdb94f..50424e9d7b4db 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -3595,7 +3595,7 @@ function mountId(): string { const treeId = getTreeId(); // Use a captial R prefix for server-generated ids. - id = ':' + identifierPrefix + 'R' + treeId; + id = '\u00AB' + identifierPrefix + 'R' + treeId; // Unless this is the first id at this level, append a number at the end // that represents the position of this useId hook among all the useId @@ -3605,11 +3605,16 @@ function mountId(): string { id += 'H' + localId.toString(32); } - id += ':'; + id += '\u00BB'; } else { // Use a lowercase r prefix for client-generated ids. const globalClientId = globalClientIdCounter++; - id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':'; + id = + '\u00AB' + + identifierPrefix + + 'r' + + globalClientId.toString(32) + + '\u00BB'; } hook.memoizedState = id; From 403d4fb852384b820a8fe405413891d8c74bbf5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 25 Feb 2025 12:45:44 -0500 Subject: [PATCH 60/73] Move ViewTransitions helpers to ReactFiberCommitViewTransitions (#32462) This doesn't change anything. It just moves some functions. This moves the view transitions helper functions into its own file. This is similar to how I already moved ReactFiberCommitEffects and ReactFiberCommitHostEffects out of ReactFiberCommitWork. This makes it a bit easier to navigate and get an overview of ReactFiberCommitWork but another motivation is also so that I can refer to these helpers from [ReactFiberApplyGesture](https://github.com/facebook/react/pull/32451/files#diff-42297cf327dee8e01d83c85314b8965953b9674e7c4615ce6c430464dcc8550b). --- .../src/ReactFiberCommitViewTransitions.js | 831 ++++++++++++++++++ .../src/ReactFiberCommitWork.js | 819 +---------------- .../src/ReactFiberWorkLoop.js | 2 +- 3 files changed, 861 insertions(+), 791 deletions(-) create mode 100644 packages/react-reconciler/src/ReactFiberCommitViewTransitions.js diff --git a/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js new file mode 100644 index 0000000000000..22b9d11ac67d2 --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js @@ -0,0 +1,831 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Instance, InstanceMeasurement, Props} from './ReactFiberConfig'; +import type {Fiber} from './ReactInternalTypes'; +import type { + ViewTransitionProps, + ViewTransitionState, +} from './ReactFiberViewTransitionComponent'; + +import { + HostComponent, + OffscreenComponent, + ViewTransitionComponent, +} from './ReactWorkTags'; +import { + NoFlags, + Update, + ViewTransitionStatic, + AffectedParentLayout, + ViewTransitionNamedStatic, +} from './ReactFiberFlags'; +import { + supportsMutation, + applyViewTransitionName, + restoreViewTransitionName, + measureInstance, + hasInstanceChanged, + hasInstanceAffectedParent, + wasInstanceInViewport, +} from './ReactFiberConfig'; +import {scheduleViewTransitionEvent} from './ReactFiberWorkLoop'; +import { + getViewTransitionName, + getViewTransitionClassName, +} from './ReactFiberViewTransitionComponent'; + +export let shouldStartViewTransition: boolean = false; + +export function resetShouldStartViewTransition(): void { + shouldStartViewTransition = false; +} + +// This tracks named ViewTransition components found in the accumulateSuspenseyCommit +// phase that might need to find deleted pairs in the beforeMutation phase. +let appearingViewTransitions: Map | null = null; + +export function resetAppearingViewTransitions(): void { + appearingViewTransitions = null; +} + +export function trackAppearingViewTransition( + name: string, + state: ViewTransitionState, +): void { + if (appearingViewTransitions === null) { + appearingViewTransitions = new Map(); + } + appearingViewTransitions.set(name, state); +} + +// We can't cancel view transition children until we know that their parent also +// don't need to transition. +export let viewTransitionCancelableChildren: null | Array< + Instance | string | Props, +> = null; // tupled array where each entry is [instance: Instance, oldName: string, props: Props] + +export function setViewTransitionCancelableChildren( + children: null | Array, +): void { + viewTransitionCancelableChildren = children; +} + +let viewTransitionHostInstanceIdx = 0; + +function applyViewTransitionToHostInstances( + child: null | Fiber, + name: string, + className: ?string, + collectMeasurements: null | Array, + stopAtNestedViewTransitions: boolean, +): boolean { + if (!supportsMutation) { + return false; + } + let inViewport = false; + while (child !== null) { + if (child.tag === HostComponent) { + shouldStartViewTransition = true; + const instance: Instance = child.stateNode; + if (collectMeasurements !== null) { + const measurement = measureInstance(instance); + collectMeasurements.push(measurement); + if (wasInstanceInViewport(measurement)) { + inViewport = true; + } + } else if (!inViewport) { + if (wasInstanceInViewport(measureInstance(instance))) { + inViewport = true; + } + } + applyViewTransitionName( + instance, + viewTransitionHostInstanceIdx === 0 + ? name + : // If we have multiple Host Instances below, we add a suffix to the name to give + // each one a unique name. + name + '_' + viewTransitionHostInstanceIdx, + className, + ); + viewTransitionHostInstanceIdx++; + } else if ( + child.tag === OffscreenComponent && + child.memoizedState !== null + ) { + // Skip any hidden subtrees. They were or are effectively not there. + } else if ( + child.tag === ViewTransitionComponent && + stopAtNestedViewTransitions + ) { + // Skip any nested view transitions for updates since in that case the + // inner most one is the one that handles the update. + } else { + if ( + applyViewTransitionToHostInstances( + child.child, + name, + className, + collectMeasurements, + stopAtNestedViewTransitions, + ) + ) { + inViewport = true; + } + } + child = child.sibling; + } + return inViewport; +} + +function restoreViewTransitionOnHostInstances( + child: null | Fiber, + stopAtNestedViewTransitions: boolean, +): void { + if (!supportsMutation) { + return; + } + while (child !== null) { + if (child.tag === HostComponent) { + const instance: Instance = child.stateNode; + restoreViewTransitionName(instance, child.memoizedProps); + } else if ( + child.tag === OffscreenComponent && + child.memoizedState !== null + ) { + // Skip any hidden subtrees. They were or are effectively not there. + } else if ( + child.tag === ViewTransitionComponent && + stopAtNestedViewTransitions + ) { + // Skip any nested view transitions for updates since in that case the + // inner most one is the one that handles the update. + } else { + restoreViewTransitionOnHostInstances( + child.child, + stopAtNestedViewTransitions, + ); + } + child = child.sibling; + } +} + +function commitAppearingPairViewTransitions(placement: Fiber): void { + if ((placement.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) { + // This has no named view transitions in its subtree. + return; + } + let child = placement.child; + while (child !== null) { + if (child.tag === OffscreenComponent && child.memoizedState === null) { + // This tree was already hidden so we skip it. + } else { + commitAppearingPairViewTransitions(child); + if ( + child.tag === ViewTransitionComponent && + (child.flags & ViewTransitionNamedStatic) !== NoFlags + ) { + const instance: ViewTransitionState = child.stateNode; + if (instance.paired) { + const props: ViewTransitionProps = child.memoizedProps; + if (props.name == null || props.name === 'auto') { + throw new Error( + 'Found a pair with an auto name. This is a bug in React.', + ); + } + const name = props.name; + const className: ?string = getViewTransitionClassName( + props.className, + props.share, + ); + if (className !== 'none') { + // We found a new appearing view transition with the same name as this deletion. + // We'll transition between them. + viewTransitionHostInstanceIdx = 0; + const inViewport = applyViewTransitionToHostInstances( + child.child, + name, + className, + null, + false, + ); + if (!inViewport) { + // This boundary is exiting within the viewport but is going to leave the viewport. + // Instead, we treat this as an exit of the previous entry by reverting the new name. + // Ideally we could undo the old transition but it's now too late. It's also on its + // on snapshot. We have know was for it to paint onto the original group. + // TODO: This will lead to things unexpectedly having exit animations that normally + // wouldn't happen. Consider if we should just let this fly off the screen instead. + restoreViewTransitionOnHostInstances(child.child, false); + } + } + } + } + } + child = child.sibling; + } +} + +export function commitEnterViewTransitions(placement: Fiber): void { + if (placement.tag === ViewTransitionComponent) { + const state: ViewTransitionState = placement.stateNode; + const props: ViewTransitionProps = placement.memoizedProps; + const name = getViewTransitionName(props, state); + const className: ?string = getViewTransitionClassName( + props.className, + state.paired ? props.share : props.enter, + ); + if (className !== 'none') { + viewTransitionHostInstanceIdx = 0; + const inViewport = applyViewTransitionToHostInstances( + placement.child, + name, + className, + null, + false, + ); + if (!inViewport) { + // TODO: If this was part of a pair we will still run the onShare callback. + // Revert the transition names. This boundary is not in the viewport + // so we won't bother animating it. + restoreViewTransitionOnHostInstances(placement.child, false); + // TODO: Should we still visit the children in case a named one was in the viewport? + } else { + commitAppearingPairViewTransitions(placement); + + if (!state.paired) { + scheduleViewTransitionEvent(placement, props.onEnter); + } + } + } else { + commitAppearingPairViewTransitions(placement); + } + } else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) { + let child = placement.child; + while (child !== null) { + commitEnterViewTransitions(child); + child = child.sibling; + } + } else { + commitAppearingPairViewTransitions(placement); + } +} + +function commitDeletedPairViewTransitions(deletion: Fiber): void { + if ( + appearingViewTransitions === null || + appearingViewTransitions.size === 0 + ) { + // We've found all. + return; + } + const pairs = appearingViewTransitions; + if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) { + // This has no named view transitions in its subtree. + return; + } + let child = deletion.child; + while (child !== null) { + if (child.tag === OffscreenComponent && child.memoizedState === null) { + // This tree was already hidden so we skip it. + } else { + if ( + child.tag === ViewTransitionComponent && + (child.flags & ViewTransitionNamedStatic) !== NoFlags + ) { + const props: ViewTransitionProps = child.memoizedProps; + const name = props.name; + if (name != null && name !== 'auto') { + const pair = pairs.get(name); + if (pair !== undefined) { + const className: ?string = getViewTransitionClassName( + props.className, + props.share, + ); + if (className !== 'none') { + // We found a new appearing view transition with the same name as this deletion. + viewTransitionHostInstanceIdx = 0; + const inViewport = applyViewTransitionToHostInstances( + child.child, + name, + className, + null, + false, + ); + if (!inViewport) { + // This boundary is not in the viewport so we won't treat it as a matched pair. + // Revert the transition names. This avoids it flying onto the screen which can + // be disruptive and doesn't really preserve any continuity anyway. + restoreViewTransitionOnHostInstances(child.child, false); + } else { + // We'll transition between them. + const oldinstance: ViewTransitionState = child.stateNode; + const newInstance: ViewTransitionState = pair; + newInstance.paired = oldinstance; + // Note: If the other side ends up outside the viewport, we'll still run this. + // Therefore it's possible for onShare to be called with only an old snapshot. + scheduleViewTransitionEvent(child, props.onShare); + } + } + // Delete the entry so that we know when we've found all of them + // and can stop searching (size reaches zero). + pairs.delete(name); + if (pairs.size === 0) { + break; + } + } + } + } + commitDeletedPairViewTransitions(child); + } + child = child.sibling; + } +} + +export function commitExitViewTransitions(deletion: Fiber): void { + if (deletion.tag === ViewTransitionComponent) { + const props: ViewTransitionProps = deletion.memoizedProps; + const name = getViewTransitionName(props, deletion.stateNode); + const pair = + appearingViewTransitions !== null + ? appearingViewTransitions.get(name) + : undefined; + const className: ?string = getViewTransitionClassName( + props.className, + pair !== undefined ? props.share : props.exit, + ); + if (className !== 'none') { + viewTransitionHostInstanceIdx = 0; + const inViewport = applyViewTransitionToHostInstances( + deletion.child, + name, + className, + null, + false, + ); + if (!inViewport) { + // Revert the transition names. This boundary is not in the viewport + // so we won't bother animating it. + restoreViewTransitionOnHostInstances(deletion.child, false); + // TODO: Should we still visit the children in case a named one was in the viewport? + } else if (pair !== undefined) { + // We found a new appearing view transition with the same name as this deletion. + // We'll transition between them instead of running the normal exit. + const oldinstance: ViewTransitionState = deletion.stateNode; + const newInstance: ViewTransitionState = pair; + newInstance.paired = oldinstance; + // Delete the entry so that we know when we've found all of them + // and can stop searching (size reaches zero). + // $FlowFixMe[incompatible-use]: Refined by the pair. + appearingViewTransitions.delete(name); + // Note: If the other side ends up outside the viewport, we'll still run this. + // Therefore it's possible for onShare to be called with only an old snapshot. + scheduleViewTransitionEvent(deletion, props.onShare); + } else { + scheduleViewTransitionEvent(deletion, props.onExit); + } + } + if (appearingViewTransitions !== null) { + // Look for more pairs deeper in the tree. + commitDeletedPairViewTransitions(deletion); + } + } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) { + let child = deletion.child; + while (child !== null) { + commitExitViewTransitions(child); + child = child.sibling; + } + } else { + if (appearingViewTransitions !== null) { + commitDeletedPairViewTransitions(deletion); + } + } +} + +export function commitBeforeUpdateViewTransition( + current: Fiber, + finishedWork: Fiber, +): void { + // The way we deal with multiple HostInstances as children of a View Transition in an + // update can get tricky. The important bit is that if you swap out n HostInstances + // from n HostInstances then they match up in order. Similarly, if you don't swap + // any HostInstances each instance just transitions as is. + // + // We call this function twice. First we apply the view transition names on the + // "current" tree in the snapshot phase. Then in the mutation phase we apply view + // transition names to the "finishedWork" tree. + // + // This means that if there were insertions or deletions before an updated Instance + // that same Instance might get different names in the "old" and the "new" state. + // For example if you swap two HostInstances inside a ViewTransition they don't + // animate to swap position but rather cross-fade into the other instance. This might + // be unexpected but it is in line with the semantics that the ViewTransition is its + // own layer that cross-fades its content when it updates. If you want to reorder then + // each child needs its own ViewTransition. + const oldProps: ViewTransitionProps = current.memoizedProps; + const oldName = getViewTransitionName(oldProps, current.stateNode); + const newProps: ViewTransitionProps = finishedWork.memoizedProps; + // This className applies only if there are fewer child DOM nodes than + // before or if this update should've been cancelled but we ended up with + // a parent animating so we need to animate the child too. + // For example, if update="foo" layout="none" and it turns out this was + // a layout only change, then the "foo" class will be applied even though + // it was not actually an update. Which is a bug. + let className: ?string = getViewTransitionClassName( + newProps.className, + newProps.update, + ); + if (className === 'none') { + className = getViewTransitionClassName(newProps.className, newProps.layout); + if (className === 'none') { + // If both update and layout are both "none" then we don't have to + // apply a name. Since we won't animate this boundary. + return; + } + } + viewTransitionHostInstanceIdx = 0; + applyViewTransitionToHostInstances( + current.child, + oldName, + className, + (current.memoizedState = []), + true, + ); +} + +export function commitNestedViewTransitions(changedParent: Fiber): void { + let child = changedParent.child; + while (child !== null) { + if (child.tag === ViewTransitionComponent) { + // In this case the outer ViewTransition component wins but if there + // was an update through this component then the inner one wins. + const props: ViewTransitionProps = child.memoizedProps; + const name = getViewTransitionName(props, child.stateNode); + const className: ?string = getViewTransitionClassName( + props.className, + props.layout, + ); + if (className !== 'none') { + viewTransitionHostInstanceIdx = 0; + applyViewTransitionToHostInstances( + child.child, + name, + className, + (child.memoizedState = []), + false, + ); + } + } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) { + commitNestedViewTransitions(child); + } + child = child.sibling; + } +} + +function restorePairedViewTransitions(parent: Fiber): void { + if ((parent.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) { + // This has no named view transitions in its subtree. + return; + } + let child = parent.child; + while (child !== null) { + if (child.tag === OffscreenComponent && child.memoizedState === null) { + // This tree was already hidden so we skip it. + } else { + if ( + child.tag === ViewTransitionComponent && + (child.flags & ViewTransitionNamedStatic) !== NoFlags + ) { + const instance: ViewTransitionState = child.stateNode; + if (instance.paired !== null) { + instance.paired = null; + restoreViewTransitionOnHostInstances(child.child, false); + } + } + restorePairedViewTransitions(child); + } + child = child.sibling; + } +} + +export function restoreEnterViewTransitions(placement: Fiber): void { + if (placement.tag === ViewTransitionComponent) { + const instance: ViewTransitionState = placement.stateNode; + instance.paired = null; + restoreViewTransitionOnHostInstances(placement.child, false); + restorePairedViewTransitions(placement); + } else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) { + let child = placement.child; + while (child !== null) { + restoreEnterViewTransitions(child); + child = child.sibling; + } + } else { + restorePairedViewTransitions(placement); + } +} + +export function restoreExitViewTransitions(deletion: Fiber): void { + if (deletion.tag === ViewTransitionComponent) { + const instance: ViewTransitionState = deletion.stateNode; + instance.paired = null; + restoreViewTransitionOnHostInstances(deletion.child, false); + restorePairedViewTransitions(deletion); + } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) { + let child = deletion.child; + while (child !== null) { + restoreExitViewTransitions(child); + child = child.sibling; + } + } else { + restorePairedViewTransitions(deletion); + } +} + +export function restoreUpdateViewTransition( + current: Fiber, + finishedWork: Fiber, +): void { + finishedWork.memoizedState = null; + restoreViewTransitionOnHostInstances(current.child, true); + restoreViewTransitionOnHostInstances(finishedWork.child, true); +} + +export function restoreNestedViewTransitions(changedParent: Fiber): void { + let child = changedParent.child; + while (child !== null) { + if (child.tag === ViewTransitionComponent) { + child.memoizedState = null; + restoreViewTransitionOnHostInstances(child.child, false); + } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) { + restoreNestedViewTransitions(child); + } + child = child.sibling; + } +} + +function cancelViewTransitionHostInstances( + currentViewTransition: Fiber, + child: null | Fiber, + stopAtNestedViewTransitions: boolean, +): void { + if (!supportsMutation) { + return; + } + while (child !== null) { + if (child.tag === HostComponent) { + const instance: Instance = child.stateNode; + const oldName = getViewTransitionName( + currentViewTransition.memoizedProps, + currentViewTransition.stateNode, + ); + if (viewTransitionCancelableChildren === null) { + viewTransitionCancelableChildren = []; + } + viewTransitionCancelableChildren.push( + instance, + oldName, + child.memoizedProps, + ); + viewTransitionHostInstanceIdx++; + } else if ( + child.tag === OffscreenComponent && + child.memoizedState !== null + ) { + // Skip any hidden subtrees. They were or are effectively not there. + } else if ( + child.tag === ViewTransitionComponent && + stopAtNestedViewTransitions + ) { + // Skip any nested view transitions for updates since in that case the + // inner most one is the one that handles the update. + } else { + cancelViewTransitionHostInstances( + currentViewTransition, + child.child, + stopAtNestedViewTransitions, + ); + } + child = child.sibling; + } +} + +function measureViewTransitionHostInstances( + currentViewTransition: Fiber, + parentViewTransition: Fiber, + child: null | Fiber, + name: string, + className: ?string, + previousMeasurements: null | Array, + stopAtNestedViewTransitions: boolean, +): boolean { + if (!supportsMutation) { + return true; + } + let inViewport = false; + while (child !== null) { + if (child.tag === HostComponent) { + const instance: Instance = child.stateNode; + if ( + previousMeasurements !== null && + viewTransitionHostInstanceIdx < previousMeasurements.length + ) { + // The previous measurement of the Instance in this location within the ViewTransition. + // Note that this might not be the same exact Instance if the Instances within the + // ViewTransition changed. + const previousMeasurement = + previousMeasurements[viewTransitionHostInstanceIdx]; + const nextMeasurement = measureInstance(instance); + if ( + wasInstanceInViewport(previousMeasurement) || + wasInstanceInViewport(nextMeasurement) + ) { + // If either the old or new state was within the viewport we have to animate this. + // But if it turns out that none of them were we'll be able to skip it. + inViewport = true; + } + if ( + (parentViewTransition.flags & Update) === NoFlags && + hasInstanceChanged(previousMeasurement, nextMeasurement) + ) { + parentViewTransition.flags |= Update; + } + if (hasInstanceAffectedParent(previousMeasurement, nextMeasurement)) { + // If this instance size within its parent has changed it might have caused the + // parent to relayout which needs a cross fade. + parentViewTransition.flags |= AffectedParentLayout; + } + } else { + // If there was an insertion of extra nodes, we have to assume they affected the parent. + // It should have already been marked as an Update due to the mutation. + parentViewTransition.flags |= AffectedParentLayout; + } + if ((parentViewTransition.flags & Update) !== NoFlags) { + // We might update this node so we need to apply its new name for the new state. + applyViewTransitionName( + instance, + viewTransitionHostInstanceIdx === 0 + ? name + : // If we have multiple Host Instances below, we add a suffix to the name to give + // each one a unique name. + name + '_' + viewTransitionHostInstanceIdx, + className, + ); + } + if (!inViewport || (parentViewTransition.flags & Update) === NoFlags) { + // It turns out that we had no other deeper mutations, the child transitions didn't + // affect the parent layout and this instance hasn't changed size. So we can skip + // animating it. However, in the current model this only works if the parent also + // doesn't animate. So we have to queue these and wait until we complete the parent + // to cancel them. + const oldName = getViewTransitionName( + currentViewTransition.memoizedProps, + currentViewTransition.stateNode, + ); + if (viewTransitionCancelableChildren === null) { + viewTransitionCancelableChildren = []; + } + viewTransitionCancelableChildren.push( + instance, + oldName, + child.memoizedProps, + ); + } + viewTransitionHostInstanceIdx++; + } else if ( + child.tag === OffscreenComponent && + child.memoizedState !== null + ) { + // Skip any hidden subtrees. They were or are effectively not there. + } else if ( + child.tag === ViewTransitionComponent && + stopAtNestedViewTransitions + ) { + // Skip any nested view transitions for updates since in that case the + // inner most one is the one that handles the update. + // If this inner boundary resized we need to bubble that information up. + parentViewTransition.flags |= child.flags & AffectedParentLayout; + } else { + if ( + measureViewTransitionHostInstances( + currentViewTransition, + parentViewTransition, + child.child, + name, + className, + previousMeasurements, + stopAtNestedViewTransitions, + ) + ) { + inViewport = true; + } + } + child = child.sibling; + } + return inViewport; +} + +export function measureUpdateViewTransition( + current: Fiber, + finishedWork: Fiber, +): boolean { + const props: ViewTransitionProps = finishedWork.memoizedProps; + const updateClassName: ?string = getViewTransitionClassName( + props.className, + props.update, + ); + const layoutClassName: ?string = getViewTransitionClassName( + props.className, + props.layout, + ); + let className: ?string; + if (updateClassName === 'none') { + if (layoutClassName === 'none') { + // If both update and layout class name were none, then we didn't apply any + // names in the before update phase so we shouldn't now neither. + return false; + } + // We don't care if this is mutated or children layout changed, but we still + // measure each instance to see if it moved and therefore should apply layout. + finishedWork.flags &= ~Update; + className = layoutClassName; + } else if ((finishedWork.flags & Update) !== NoFlags) { + // It was updated and we have an appropriate class name to apply. + className = updateClassName; + } else { + if (layoutClassName === 'none') { + // If we did not update, then all changes are considered a layout. We'll + // attempt to cancel. + viewTransitionHostInstanceIdx = 0; + cancelViewTransitionHostInstances(current, finishedWork.child, true); + return false; + } + // We didn't update but we might still apply layout so we measure each + // instance to see if it moved or resized. + className = layoutClassName; + } + const name = getViewTransitionName(props, finishedWork.stateNode); + // If nothing changed due to a mutation, or children changing size + // and the measurements end up unchanged, we should restore it to not animate. + viewTransitionHostInstanceIdx = 0; + const previousMeasurements = current.memoizedState; + const inViewport = measureViewTransitionHostInstances( + current, + finishedWork, + finishedWork.child, + name, + className, + previousMeasurements, + true, + ); + const previousCount = + previousMeasurements === null ? 0 : previousMeasurements.length; + if (viewTransitionHostInstanceIdx !== previousCount) { + // If we found a different number of child DOM nodes we need to assume that + // the parent layout may have changed as a result. This is not necessarily + // true if those nodes were absolutely positioned. + finishedWork.flags |= AffectedParentLayout; + } + return inViewport; +} + +export function measureNestedViewTransitions(changedParent: Fiber): void { + let child = changedParent.child; + while (child !== null) { + if (child.tag === ViewTransitionComponent) { + const current = child.alternate; + if (current !== null) { + const props: ViewTransitionProps = child.memoizedProps; + const name = getViewTransitionName(props, child.stateNode); + const className: ?string = getViewTransitionClassName( + props.className, + props.layout, + ); + viewTransitionHostInstanceIdx = 0; + const inViewport = measureViewTransitionHostInstances( + current, + child, + child.child, + name, + className, + child.memoizedState, + false, + ); + if ((child.flags & Update) === NoFlags || !inViewport) { + // Nothing changed. + } else { + scheduleViewTransitionEvent(child, props.onLayout); + } + } + } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) { + measureNestedViewTransitions(child); + } + child = child.sibling; + } +} diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 850f3c67067ff..f5d0987393046 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -14,7 +14,6 @@ import type { Container, HoistableRoot, FormInstance, - InstanceMeasurement, Props, } from './ReactFiberConfig'; import type {Fiber, FiberRoot} from './ReactInternalTypes'; @@ -112,9 +111,7 @@ import { PerformedWork, ForceClientRender, DidCapture, - ViewTransitionStatic, AffectedParentLayout, - ViewTransitionNamedStatic, } from './ReactFiberFlags'; import { commitStartTime, @@ -163,15 +160,9 @@ import { suspendResource, resetFormInstance, registerSuspenseInstanceRetry, - applyViewTransitionName, - restoreViewTransitionName, cancelViewTransitionName, cancelRootViewTransitionName, restoreRootViewTransitionName, - measureInstance, - hasInstanceChanged, - hasInstanceAffectedParent, - wasInstanceInViewport, isSingletonScope, } from './ReactFiberConfig'; import { @@ -203,10 +194,6 @@ import { OffscreenDetached, OffscreenPassiveEffectsConnected, } from './ReactFiberActivityComponent'; -import { - getViewTransitionName, - getViewTransitionClassName, -} from './ReactFiberViewTransitionComponent'; import { TransitionRoot, TransitionTracingMarker, @@ -249,6 +236,23 @@ import { commitHostSingletonAcquisition, commitHostSingletonRelease, } from './ReactFiberCommitHostEffects'; +import { + commitEnterViewTransitions, + commitExitViewTransitions, + commitBeforeUpdateViewTransition, + commitNestedViewTransitions, + restoreEnterViewTransitions, + restoreExitViewTransitions, + restoreUpdateViewTransition, + restoreNestedViewTransitions, + measureUpdateViewTransition, + measureNestedViewTransitions, + resetShouldStartViewTransition, + resetAppearingViewTransitions, + trackAppearingViewTransition, + viewTransitionCancelableChildren, + setViewTransitionCancelableChildren, +} from './ReactFiberCommitViewTransitions'; import { viewTransitionMutationContext, pushMutationContext, @@ -274,19 +278,9 @@ let inProgressRoot: FiberRoot | null = null; let focusedInstanceHandle: null | Fiber = null; export let shouldFireAfterActiveInstanceBlur: boolean = false; -export let shouldStartViewTransition: boolean = false; - -// This tracks named ViewTransition components found in the accumulateSuspenseyCommit -// phase that might need to find deleted pairs in the beforeMutation phase. -let appearingViewTransitions: Map | null = null; - // Used during the commit phase to track whether a parent ViewTransition component // might have been affected by any mutations / relayouts below. let viewTransitionContextChanged: boolean = false; -// We can't cancel view transition children until we know that their parent also -// don't need to transition. -let viewTransitionCancelableChildren: null | Array = - null; // tupled array where each entry is [instance: Instance, oldName: string, props: Props] export function commitBeforeMutationEffects( root: FiberRoot, @@ -295,7 +289,8 @@ export function commitBeforeMutationEffects( ): void { focusedInstanceHandle = prepareForCommit(root.containerInfo); shouldFireAfterActiveInstanceBlur = false; - shouldStartViewTransition = false; + + resetShouldStartViewTransition(); const isViewTransitionEligible = enableViewTransition && @@ -307,7 +302,7 @@ export function commitBeforeMutationEffects( // We no longer need to track the active instance fiber focusedInstanceHandle = null; // We've found any matched pairs and can now reset. - appearingViewTransitions = null; + resetAppearingViewTransitions(); } function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) { @@ -542,759 +537,6 @@ function commitBeforeMutationEffectsDeletion( } } -let viewTransitionHostInstanceIdx = 0; - -function applyViewTransitionToHostInstances( - child: null | Fiber, - name: string, - className: ?string, - collectMeasurements: null | Array, - stopAtNestedViewTransitions: boolean, -): boolean { - if (!supportsMutation) { - return false; - } - let inViewport = false; - while (child !== null) { - if (child.tag === HostComponent) { - shouldStartViewTransition = true; - const instance: Instance = child.stateNode; - if (collectMeasurements !== null) { - const measurement = measureInstance(instance); - collectMeasurements.push(measurement); - if (wasInstanceInViewport(measurement)) { - inViewport = true; - } - } else if (!inViewport) { - if (wasInstanceInViewport(measureInstance(instance))) { - inViewport = true; - } - } - applyViewTransitionName( - instance, - viewTransitionHostInstanceIdx === 0 - ? name - : // If we have multiple Host Instances below, we add a suffix to the name to give - // each one a unique name. - name + '_' + viewTransitionHostInstanceIdx, - className, - ); - viewTransitionHostInstanceIdx++; - } else if ( - child.tag === OffscreenComponent && - child.memoizedState !== null - ) { - // Skip any hidden subtrees. They were or are effectively not there. - } else if ( - child.tag === ViewTransitionComponent && - stopAtNestedViewTransitions - ) { - // Skip any nested view transitions for updates since in that case the - // inner most one is the one that handles the update. - } else { - if ( - applyViewTransitionToHostInstances( - child.child, - name, - className, - collectMeasurements, - stopAtNestedViewTransitions, - ) - ) { - inViewport = true; - } - } - child = child.sibling; - } - return inViewport; -} - -function restoreViewTransitionOnHostInstances( - child: null | Fiber, - stopAtNestedViewTransitions: boolean, -): void { - if (!supportsMutation) { - return; - } - while (child !== null) { - if (child.tag === HostComponent) { - const instance: Instance = child.stateNode; - restoreViewTransitionName(instance, child.memoizedProps); - } else if ( - child.tag === OffscreenComponent && - child.memoizedState !== null - ) { - // Skip any hidden subtrees. They were or are effectively not there. - } else if ( - child.tag === ViewTransitionComponent && - stopAtNestedViewTransitions - ) { - // Skip any nested view transitions for updates since in that case the - // inner most one is the one that handles the update. - } else { - restoreViewTransitionOnHostInstances( - child.child, - stopAtNestedViewTransitions, - ); - } - child = child.sibling; - } -} - -function commitAppearingPairViewTransitions(placement: Fiber): void { - if ((placement.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) { - // This has no named view transitions in its subtree. - return; - } - let child = placement.child; - while (child !== null) { - if (child.tag === OffscreenComponent && child.memoizedState === null) { - // This tree was already hidden so we skip it. - } else { - commitAppearingPairViewTransitions(child); - if ( - child.tag === ViewTransitionComponent && - (child.flags & ViewTransitionNamedStatic) !== NoFlags - ) { - const instance: ViewTransitionState = child.stateNode; - if (instance.paired) { - const props: ViewTransitionProps = child.memoizedProps; - if (props.name == null || props.name === 'auto') { - throw new Error( - 'Found a pair with an auto name. This is a bug in React.', - ); - } - const name = props.name; - const className: ?string = getViewTransitionClassName( - props.className, - props.share, - ); - if (className !== 'none') { - // We found a new appearing view transition with the same name as this deletion. - // We'll transition between them. - viewTransitionHostInstanceIdx = 0; - const inViewport = applyViewTransitionToHostInstances( - child.child, - name, - className, - null, - false, - ); - if (!inViewport) { - // This boundary is exiting within the viewport but is going to leave the viewport. - // Instead, we treat this as an exit of the previous entry by reverting the new name. - // Ideally we could undo the old transition but it's now too late. It's also on its - // on snapshot. We have know was for it to paint onto the original group. - // TODO: This will lead to things unexpectedly having exit animations that normally - // wouldn't happen. Consider if we should just let this fly off the screen instead. - restoreViewTransitionOnHostInstances(child.child, false); - } - } - } - } - } - child = child.sibling; - } -} - -function commitEnterViewTransitions(placement: Fiber): void { - if (placement.tag === ViewTransitionComponent) { - const state: ViewTransitionState = placement.stateNode; - const props: ViewTransitionProps = placement.memoizedProps; - const name = getViewTransitionName(props, state); - const className: ?string = getViewTransitionClassName( - props.className, - state.paired ? props.share : props.enter, - ); - if (className !== 'none') { - viewTransitionHostInstanceIdx = 0; - const inViewport = applyViewTransitionToHostInstances( - placement.child, - name, - className, - null, - false, - ); - if (!inViewport) { - // TODO: If this was part of a pair we will still run the onShare callback. - // Revert the transition names. This boundary is not in the viewport - // so we won't bother animating it. - restoreViewTransitionOnHostInstances(placement.child, false); - // TODO: Should we still visit the children in case a named one was in the viewport? - } else { - commitAppearingPairViewTransitions(placement); - - if (!state.paired) { - scheduleViewTransitionEvent(placement, props.onEnter); - } - } - } else { - commitAppearingPairViewTransitions(placement); - } - } else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) { - let child = placement.child; - while (child !== null) { - commitEnterViewTransitions(child); - child = child.sibling; - } - } else { - commitAppearingPairViewTransitions(placement); - } -} - -function commitDeletedPairViewTransitions(deletion: Fiber): void { - if ( - appearingViewTransitions === null || - appearingViewTransitions.size === 0 - ) { - // We've found all. - return; - } - const pairs = appearingViewTransitions; - if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) { - // This has no named view transitions in its subtree. - return; - } - let child = deletion.child; - while (child !== null) { - if (child.tag === OffscreenComponent && child.memoizedState === null) { - // This tree was already hidden so we skip it. - } else { - if ( - child.tag === ViewTransitionComponent && - (child.flags & ViewTransitionNamedStatic) !== NoFlags - ) { - const props: ViewTransitionProps = child.memoizedProps; - const name = props.name; - if (name != null && name !== 'auto') { - const pair = pairs.get(name); - if (pair !== undefined) { - const className: ?string = getViewTransitionClassName( - props.className, - props.share, - ); - if (className !== 'none') { - // We found a new appearing view transition with the same name as this deletion. - viewTransitionHostInstanceIdx = 0; - const inViewport = applyViewTransitionToHostInstances( - child.child, - name, - className, - null, - false, - ); - if (!inViewport) { - // This boundary is not in the viewport so we won't treat it as a matched pair. - // Revert the transition names. This avoids it flying onto the screen which can - // be disruptive and doesn't really preserve any continuity anyway. - restoreViewTransitionOnHostInstances(child.child, false); - } else { - // We'll transition between them. - const oldinstance: ViewTransitionState = child.stateNode; - const newInstance: ViewTransitionState = pair; - newInstance.paired = oldinstance; - // Note: If the other side ends up outside the viewport, we'll still run this. - // Therefore it's possible for onShare to be called with only an old snapshot. - scheduleViewTransitionEvent(child, props.onShare); - } - } - // Delete the entry so that we know when we've found all of them - // and can stop searching (size reaches zero). - pairs.delete(name); - if (pairs.size === 0) { - break; - } - } - } - } - commitDeletedPairViewTransitions(child); - } - child = child.sibling; - } -} - -function commitExitViewTransitions(deletion: Fiber): void { - if (deletion.tag === ViewTransitionComponent) { - const props: ViewTransitionProps = deletion.memoizedProps; - const name = getViewTransitionName(props, deletion.stateNode); - const pair = - appearingViewTransitions !== null - ? appearingViewTransitions.get(name) - : undefined; - const className: ?string = getViewTransitionClassName( - props.className, - pair !== undefined ? props.share : props.exit, - ); - if (className !== 'none') { - viewTransitionHostInstanceIdx = 0; - const inViewport = applyViewTransitionToHostInstances( - deletion.child, - name, - className, - null, - false, - ); - if (!inViewport) { - // Revert the transition names. This boundary is not in the viewport - // so we won't bother animating it. - restoreViewTransitionOnHostInstances(deletion.child, false); - // TODO: Should we still visit the children in case a named one was in the viewport? - } else if (pair !== undefined) { - // We found a new appearing view transition with the same name as this deletion. - // We'll transition between them instead of running the normal exit. - const oldinstance: ViewTransitionState = deletion.stateNode; - const newInstance: ViewTransitionState = pair; - newInstance.paired = oldinstance; - // Delete the entry so that we know when we've found all of them - // and can stop searching (size reaches zero). - // $FlowFixMe[incompatible-use]: Refined by the pair. - appearingViewTransitions.delete(name); - // Note: If the other side ends up outside the viewport, we'll still run this. - // Therefore it's possible for onShare to be called with only an old snapshot. - scheduleViewTransitionEvent(deletion, props.onShare); - } else { - scheduleViewTransitionEvent(deletion, props.onExit); - } - } - if (appearingViewTransitions !== null) { - // Look for more pairs deeper in the tree. - commitDeletedPairViewTransitions(deletion); - } - } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) { - let child = deletion.child; - while (child !== null) { - commitExitViewTransitions(child); - child = child.sibling; - } - } else { - if (appearingViewTransitions !== null) { - commitDeletedPairViewTransitions(deletion); - } - } -} - -function commitBeforeUpdateViewTransition( - current: Fiber, - finishedWork: Fiber, -): void { - // The way we deal with multiple HostInstances as children of a View Transition in an - // update can get tricky. The important bit is that if you swap out n HostInstances - // from n HostInstances then they match up in order. Similarly, if you don't swap - // any HostInstances each instance just transitions as is. - // - // We call this function twice. First we apply the view transition names on the - // "current" tree in the snapshot phase. Then in the mutation phase we apply view - // transition names to the "finishedWork" tree. - // - // This means that if there were insertions or deletions before an updated Instance - // that same Instance might get different names in the "old" and the "new" state. - // For example if you swap two HostInstances inside a ViewTransition they don't - // animate to swap position but rather cross-fade into the other instance. This might - // be unexpected but it is in line with the semantics that the ViewTransition is its - // own layer that cross-fades its content when it updates. If you want to reorder then - // each child needs its own ViewTransition. - const oldProps: ViewTransitionProps = current.memoizedProps; - const oldName = getViewTransitionName(oldProps, current.stateNode); - const newProps: ViewTransitionProps = finishedWork.memoizedProps; - // This className applies only if there are fewer child DOM nodes than - // before or if this update should've been cancelled but we ended up with - // a parent animating so we need to animate the child too. - // For example, if update="foo" layout="none" and it turns out this was - // a layout only change, then the "foo" class will be applied even though - // it was not actually an update. Which is a bug. - let className: ?string = getViewTransitionClassName( - newProps.className, - newProps.update, - ); - if (className === 'none') { - className = getViewTransitionClassName(newProps.className, newProps.layout); - if (className === 'none') { - // If both update and layout are both "none" then we don't have to - // apply a name. Since we won't animate this boundary. - return; - } - } - viewTransitionHostInstanceIdx = 0; - applyViewTransitionToHostInstances( - current.child, - oldName, - className, - (current.memoizedState = []), - true, - ); -} - -function commitNestedViewTransitions(changedParent: Fiber): void { - let child = changedParent.child; - while (child !== null) { - if (child.tag === ViewTransitionComponent) { - // In this case the outer ViewTransition component wins but if there - // was an update through this component then the inner one wins. - const props: ViewTransitionProps = child.memoizedProps; - const name = getViewTransitionName(props, child.stateNode); - const className: ?string = getViewTransitionClassName( - props.className, - props.layout, - ); - if (className !== 'none') { - viewTransitionHostInstanceIdx = 0; - applyViewTransitionToHostInstances( - child.child, - name, - className, - (child.memoizedState = []), - false, - ); - } - } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) { - commitNestedViewTransitions(child); - } - child = child.sibling; - } -} - -function restorePairedViewTransitions(parent: Fiber): void { - if ((parent.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) { - // This has no named view transitions in its subtree. - return; - } - let child = parent.child; - while (child !== null) { - if (child.tag === OffscreenComponent && child.memoizedState === null) { - // This tree was already hidden so we skip it. - } else { - if ( - child.tag === ViewTransitionComponent && - (child.flags & ViewTransitionNamedStatic) !== NoFlags - ) { - const instance: ViewTransitionState = child.stateNode; - if (instance.paired !== null) { - instance.paired = null; - restoreViewTransitionOnHostInstances(child.child, false); - } - } - restorePairedViewTransitions(child); - } - child = child.sibling; - } -} - -function restoreEnterViewTransitions(placement: Fiber): void { - if (placement.tag === ViewTransitionComponent) { - const instance: ViewTransitionState = placement.stateNode; - instance.paired = null; - restoreViewTransitionOnHostInstances(placement.child, false); - restorePairedViewTransitions(placement); - } else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) { - let child = placement.child; - while (child !== null) { - restoreEnterViewTransitions(child); - child = child.sibling; - } - } else { - restorePairedViewTransitions(placement); - } -} - -function restoreExitViewTransitions(deletion: Fiber): void { - if (deletion.tag === ViewTransitionComponent) { - const instance: ViewTransitionState = deletion.stateNode; - instance.paired = null; - restoreViewTransitionOnHostInstances(deletion.child, false); - restorePairedViewTransitions(deletion); - } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) { - let child = deletion.child; - while (child !== null) { - restoreExitViewTransitions(child); - child = child.sibling; - } - } else { - restorePairedViewTransitions(deletion); - } -} - -function restoreUpdateViewTransition( - current: Fiber, - finishedWork: Fiber, -): void { - finishedWork.memoizedState = null; - restoreViewTransitionOnHostInstances(current.child, true); - restoreViewTransitionOnHostInstances(finishedWork.child, true); -} - -function restoreNestedViewTransitions(changedParent: Fiber): void { - let child = changedParent.child; - while (child !== null) { - if (child.tag === ViewTransitionComponent) { - child.memoizedState = null; - restoreViewTransitionOnHostInstances(child.child, false); - } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) { - restoreNestedViewTransitions(child); - } - child = child.sibling; - } -} - -function cancelViewTransitionHostInstances( - currentViewTransition: Fiber, - child: null | Fiber, - stopAtNestedViewTransitions: boolean, -): void { - if (!supportsMutation) { - return; - } - while (child !== null) { - if (child.tag === HostComponent) { - const instance: Instance = child.stateNode; - const oldName = getViewTransitionName( - currentViewTransition.memoizedProps, - currentViewTransition.stateNode, - ); - if (viewTransitionCancelableChildren === null) { - viewTransitionCancelableChildren = []; - } - viewTransitionCancelableChildren.push( - instance, - oldName, - child.memoizedProps, - ); - viewTransitionHostInstanceIdx++; - } else if ( - child.tag === OffscreenComponent && - child.memoizedState !== null - ) { - // Skip any hidden subtrees. They were or are effectively not there. - } else if ( - child.tag === ViewTransitionComponent && - stopAtNestedViewTransitions - ) { - // Skip any nested view transitions for updates since in that case the - // inner most one is the one that handles the update. - } else { - cancelViewTransitionHostInstances( - currentViewTransition, - child.child, - stopAtNestedViewTransitions, - ); - } - child = child.sibling; - } -} - -function measureViewTransitionHostInstances( - currentViewTransition: Fiber, - parentViewTransition: Fiber, - child: null | Fiber, - name: string, - className: ?string, - previousMeasurements: null | Array, - stopAtNestedViewTransitions: boolean, -): boolean { - if (!supportsMutation) { - return true; - } - let inViewport = false; - while (child !== null) { - if (child.tag === HostComponent) { - const instance: Instance = child.stateNode; - if ( - previousMeasurements !== null && - viewTransitionHostInstanceIdx < previousMeasurements.length - ) { - // The previous measurement of the Instance in this location within the ViewTransition. - // Note that this might not be the same exact Instance if the Instances within the - // ViewTransition changed. - const previousMeasurement = - previousMeasurements[viewTransitionHostInstanceIdx]; - const nextMeasurement = measureInstance(instance); - if ( - wasInstanceInViewport(previousMeasurement) || - wasInstanceInViewport(nextMeasurement) - ) { - // If either the old or new state was within the viewport we have to animate this. - // But if it turns out that none of them were we'll be able to skip it. - inViewport = true; - } - if ( - (parentViewTransition.flags & Update) === NoFlags && - hasInstanceChanged(previousMeasurement, nextMeasurement) - ) { - parentViewTransition.flags |= Update; - } - if (hasInstanceAffectedParent(previousMeasurement, nextMeasurement)) { - // If this instance size within its parent has changed it might have caused the - // parent to relayout which needs a cross fade. - parentViewTransition.flags |= AffectedParentLayout; - } - } else { - // If there was an insertion of extra nodes, we have to assume they affected the parent. - // It should have already been marked as an Update due to the mutation. - parentViewTransition.flags |= AffectedParentLayout; - } - if ((parentViewTransition.flags & Update) !== NoFlags) { - // We might update this node so we need to apply its new name for the new state. - applyViewTransitionName( - instance, - viewTransitionHostInstanceIdx === 0 - ? name - : // If we have multiple Host Instances below, we add a suffix to the name to give - // each one a unique name. - name + '_' + viewTransitionHostInstanceIdx, - className, - ); - } - if (!inViewport || (parentViewTransition.flags & Update) === NoFlags) { - // It turns out that we had no other deeper mutations, the child transitions didn't - // affect the parent layout and this instance hasn't changed size. So we can skip - // animating it. However, in the current model this only works if the parent also - // doesn't animate. So we have to queue these and wait until we complete the parent - // to cancel them. - const oldName = getViewTransitionName( - currentViewTransition.memoizedProps, - currentViewTransition.stateNode, - ); - if (viewTransitionCancelableChildren === null) { - viewTransitionCancelableChildren = []; - } - viewTransitionCancelableChildren.push( - instance, - oldName, - child.memoizedProps, - ); - } - viewTransitionHostInstanceIdx++; - } else if ( - child.tag === OffscreenComponent && - child.memoizedState !== null - ) { - // Skip any hidden subtrees. They were or are effectively not there. - } else if ( - child.tag === ViewTransitionComponent && - stopAtNestedViewTransitions - ) { - // Skip any nested view transitions for updates since in that case the - // inner most one is the one that handles the update. - // If this inner boundary resized we need to bubble that information up. - parentViewTransition.flags |= child.flags & AffectedParentLayout; - } else { - if ( - measureViewTransitionHostInstances( - currentViewTransition, - parentViewTransition, - child.child, - name, - className, - previousMeasurements, - stopAtNestedViewTransitions, - ) - ) { - inViewport = true; - } - } - child = child.sibling; - } - return inViewport; -} - -function measureUpdateViewTransition( - current: Fiber, - finishedWork: Fiber, -): boolean { - const props: ViewTransitionProps = finishedWork.memoizedProps; - const updateClassName: ?string = getViewTransitionClassName( - props.className, - props.update, - ); - const layoutClassName: ?string = getViewTransitionClassName( - props.className, - props.layout, - ); - let className: ?string; - if (updateClassName === 'none') { - if (layoutClassName === 'none') { - // If both update and layout class name were none, then we didn't apply any - // names in the before update phase so we shouldn't now neither. - return false; - } - // We don't care if this is mutated or children layout changed, but we still - // measure each instance to see if it moved and therefore should apply layout. - finishedWork.flags &= ~Update; - className = layoutClassName; - } else if ((finishedWork.flags & Update) !== NoFlags) { - // It was updated and we have an appropriate class name to apply. - className = updateClassName; - } else { - if (layoutClassName === 'none') { - // If we did not update, then all changes are considered a layout. We'll - // attempt to cancel. - viewTransitionHostInstanceIdx = 0; - cancelViewTransitionHostInstances(current, finishedWork.child, true); - return false; - } - // We didn't update but we might still apply layout so we measure each - // instance to see if it moved or resized. - className = layoutClassName; - } - const name = getViewTransitionName(props, finishedWork.stateNode); - // If nothing changed due to a mutation, or children changing size - // and the measurements end up unchanged, we should restore it to not animate. - viewTransitionHostInstanceIdx = 0; - const previousMeasurements = current.memoizedState; - const inViewport = measureViewTransitionHostInstances( - current, - finishedWork, - finishedWork.child, - name, - className, - previousMeasurements, - true, - ); - const previousCount = - previousMeasurements === null ? 0 : previousMeasurements.length; - if (viewTransitionHostInstanceIdx !== previousCount) { - // If we found a different number of child DOM nodes we need to assume that - // the parent layout may have changed as a result. This is not necessarily - // true if those nodes were absolutely positioned. - finishedWork.flags |= AffectedParentLayout; - } - return inViewport; -} - -function measureNestedViewTransitions(changedParent: Fiber): void { - let child = changedParent.child; - while (child !== null) { - if (child.tag === ViewTransitionComponent) { - const current = child.alternate; - if (current !== null) { - const props: ViewTransitionProps = child.memoizedProps; - const name = getViewTransitionName(props, child.stateNode); - const className: ?string = getViewTransitionClassName( - props.className, - props.layout, - ); - viewTransitionHostInstanceIdx = 0; - const inViewport = measureViewTransitionHostInstances( - current, - child, - child.child, - name, - className, - child.memoizedState, - false, - ); - if ((child.flags & Update) === NoFlags || !inViewport) { - // Nothing changed. - } else { - scheduleViewTransitionEvent(child, props.onLayout); - } - } - } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) { - measureNestedViewTransitions(child); - } - child = child.sibling; - } -} - function commitLayoutEffectOnFiber( finishedRoot: FiberRoot, current: Fiber | null, @@ -3204,14 +2446,14 @@ function commitAfterMutationEffectsOnFiber( switch (finishedWork.tag) { case HostRoot: { viewTransitionContextChanged = false; - viewTransitionCancelableChildren = null; + setViewTransitionCancelableChildren(null); recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes); if (!viewTransitionContextChanged) { // If we didn't leak any resizing out to the root, we don't have to transition // the root itself. This means that we can now safely cancel any cancellations // that bubbled all the way up. const cancelableChildren = viewTransitionCancelableChildren; - viewTransitionCancelableChildren = null; + setViewTransitionCancelableChildren(null); if (cancelableChildren !== null) { for (let i = 0; i < cancelableChildren.length; i += 3) { cancelViewTransitionName( @@ -3264,7 +2506,7 @@ function commitAfterMutationEffectsOnFiber( const prevContextChanged = viewTransitionContextChanged; const prevCancelableChildren = viewTransitionCancelableChildren; viewTransitionContextChanged = false; - viewTransitionCancelableChildren = null; + setViewTransitionCancelableChildren(null); recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes); if (viewTransitionContextChanged) { @@ -3287,7 +2529,7 @@ function commitAfterMutationEffectsOnFiber( prevCancelableChildren, viewTransitionCancelableChildren, ); - viewTransitionCancelableChildren = prevCancelableChildren; + setViewTransitionCancelableChildren(prevCancelableChildren); } // TODO: If this doesn't end up canceled, because a parent animates, // then we should probably issue an event since this instance is part of it. @@ -3301,7 +2543,7 @@ function commitAfterMutationEffectsOnFiber( ); // If this boundary did update, we cannot cancel its children so those are dropped. - viewTransitionCancelableChildren = prevCancelableChildren; + setViewTransitionCancelableChildren(prevCancelableChildren); } if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) { @@ -4810,7 +4052,7 @@ export function commitPassiveUnmountEffects(finishedWork: Fiber): void { // pairs. let suspenseyCommitFlag = ShouldSuspendCommit; export function accumulateSuspenseyCommit(finishedWork: Fiber): void { - appearingViewTransitions = null; + resetAppearingViewTransitions(); accumulateSuspenseyCommitOnFiber(finishedWork); } @@ -4897,14 +4139,11 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber) { if (name != null && name !== 'auto') { // This is a named ViewTransition being mounted or reappearing. Let's add it to // the map so we can match it with deletions later. - if (appearingViewTransitions === null) { - appearingViewTransitions = new Map(); - } + const state: ViewTransitionState = fiber.stateNode; // Reset the pair in case we didn't end up restoring the instance in previous commits. // This shouldn't really happen anymore but just in case. We could maybe add an invariant. - const instance: ViewTransitionState = fiber.stateNode; - instance.paired = null; - appearingViewTransitions.set(name, instance); + state.paired = null; + trackAppearingViewTransition(name, state); } } recursivelyAccumulateSuspenseyCommit(fiber); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index b3d1e5371c06c..d5ef003e3dc61 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -226,8 +226,8 @@ import { invokeLayoutEffectUnmountInDEV, invokePassiveEffectUnmountInDEV, accumulateSuspenseyCommit, - shouldStartViewTransition, } from './ReactFiberCommitWork'; +import {shouldStartViewTransition} from './ReactFiberCommitViewTransitions'; import {enqueueUpdate} from './ReactFiberClassUpdateQueue'; import {resetContextDependencies} from './ReactFiberNewContext'; import { From 92e65ca68f6bfc6be515ccacaa918e33b63911df Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 25 Feb 2025 18:55:49 -0500 Subject: [PATCH 61/73] [forgive] Add basic codelens provider (#32476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a first codelens provider for successfully compiled functions. A later PR will add an actual command that will fire when the codelens is clicked ![Screenshot 2025-02-25 at 6 40 20 PM](https://github.com/user-attachments/assets/924586e0-f70a-45d1-b0e6-a89af9371c8d) --- .../src/Entrypoint/Options.ts | 77 ++++++++++-------- .../server/src/compiler/compat.ts | 22 +++++ .../server/src/compiler/index.ts | 12 ++- .../react-forgive/server/src/index.ts | 80 +++++++++++++------ 4 files changed, 129 insertions(+), 62 deletions(-) create mode 100644 compiler/packages/react-forgive/server/src/compiler/compat.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 35c2c4134eb44..781abd05f35da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -176,41 +176,48 @@ export type CompilationMode = z.infer; * babel or other unhandled exceptions). */ export type LoggerEvent = - | { - kind: 'CompileError'; - fnLoc: t.SourceLocation | null; - detail: CompilerErrorDetailOptions; - } - | { - kind: 'CompileDiagnostic'; - fnLoc: t.SourceLocation | null; - detail: Omit, 'suggestions'>; - } - | { - kind: 'CompileSkip'; - fnLoc: t.SourceLocation | null; - reason: string; - loc: t.SourceLocation | null; - } - | { - kind: 'CompileSuccess'; - fnLoc: t.SourceLocation | null; - fnName: string | null; - memoSlots: number; - memoBlocks: number; - memoValues: number; - prunedMemoBlocks: number; - prunedMemoValues: number; - } - | { - kind: 'PipelineError'; - fnLoc: t.SourceLocation | null; - data: string; - } - | { - kind: 'Timing'; - measurement: PerformanceMeasure; - }; + | CompileSuccessEvent + | CompileErrorEvent + | CompileDiagnosticEvent + | CompileSkipEvent + | PipelineErrorEvent + | TimingEvent; + +export type CompileErrorEvent = { + kind: 'CompileError'; + fnLoc: t.SourceLocation | null; + detail: CompilerErrorDetailOptions; +}; +export type CompileDiagnosticEvent = { + kind: 'CompileDiagnostic'; + fnLoc: t.SourceLocation | null; + detail: Omit, 'suggestions'>; +}; +export type CompileSuccessEvent = { + kind: 'CompileSuccess'; + fnLoc: t.SourceLocation | null; + fnName: string | null; + memoSlots: number; + memoBlocks: number; + memoValues: number; + prunedMemoBlocks: number; + prunedMemoValues: number; +}; +export type CompileSkipEvent = { + kind: 'CompileSkip'; + fnLoc: t.SourceLocation | null; + reason: string; + loc: t.SourceLocation | null; +}; +export type PipelineErrorEvent = { + kind: 'PipelineError'; + fnLoc: t.SourceLocation | null; + data: string; +}; +export type TimingEvent = { + kind: 'Timing'; + measurement: PerformanceMeasure; +}; export type Logger = { logEvent: (filename: string | null, event: LoggerEvent) => void; diff --git a/compiler/packages/react-forgive/server/src/compiler/compat.ts b/compiler/packages/react-forgive/server/src/compiler/compat.ts new file mode 100644 index 0000000000000..8b13f1df886ea --- /dev/null +++ b/compiler/packages/react-forgive/server/src/compiler/compat.ts @@ -0,0 +1,22 @@ +import {SourceLocation} from 'babel-plugin-react-compiler/src'; +import {type Range} from 'vscode-languageserver'; + +export function babelLocationToRange(loc: SourceLocation): Range | null { + if (typeof loc === 'symbol') { + return null; + } + return { + start: {line: loc.start.line - 1, character: loc.start.column}, + end: {line: loc.end.line - 1, character: loc.end.column}, + }; +} + +/** + * Refine range to only the first character. + */ +export function getRangeFirstCharacter(range: Range): Range { + return { + start: range.start, + end: range.start, + }; +} diff --git a/compiler/packages/react-forgive/server/src/compiler/index.ts b/compiler/packages/react-forgive/server/src/compiler/index.ts index be2cca94ca154..3723785cfbebd 100644 --- a/compiler/packages/react-forgive/server/src/compiler/index.ts +++ b/compiler/packages/react-forgive/server/src/compiler/index.ts @@ -11,10 +11,12 @@ import BabelPluginReactCompiler, { type PluginOptions, } from 'babel-plugin-react-compiler/src'; import * as babelParser from 'prettier/plugins/babel.js'; -import * as estreeParser from 'prettier/plugins/estree'; +import estreeParser from 'prettier/plugins/estree'; import * as typescriptParser from 'prettier/plugins/typescript'; import * as prettier from 'prettier/standalone'; +export let lastResult: BabelCore.BabelFileResult | null = null; + type CompileOptions = { text: string; file: string; @@ -24,7 +26,7 @@ export async function compile({ text, file, options, -}: CompileOptions): Promise { +}: CompileOptions): Promise { const ast = await parseAsync(text, { sourceFileName: file, parserOpts: { @@ -32,6 +34,9 @@ export async function compile({ }, sourceType: 'module', }); + if (ast == null) { + return null; + } const plugins = options != null ? [[BabelPluginReactCompiler, options]] @@ -54,5 +59,8 @@ export async function compile({ parser: 'babel-ts', plugins: [babelParser, estreeParser, typescriptParser], }); + if (result.code != null) { + lastResult = result; + } return result; } diff --git a/compiler/packages/react-forgive/server/src/index.ts b/compiler/packages/react-forgive/server/src/index.ts index 057df617c8d10..395969c5e06bf 100644 --- a/compiler/packages/react-forgive/server/src/index.ts +++ b/compiler/packages/react-forgive/server/src/index.ts @@ -7,6 +7,7 @@ import {TextDocument} from 'vscode-languageserver-textdocument'; import { + CodeLens, createConnection, type InitializeParams, type InitializeResult, @@ -14,10 +15,15 @@ import { TextDocuments, TextDocumentSyncKind, } from 'vscode-languageserver/node'; -import {compile} from './compiler'; +import {compile, lastResult} from './compiler'; import {type PluginOptions} from 'babel-plugin-react-compiler/src'; import {resolveReactConfig} from './compiler/options'; -import {type BabelFileResult} from '@babel/core'; +import { + CompileSuccessEvent, + defaultOptions, + LoggerEvent, +} from 'babel-plugin-react-compiler/src/Entrypoint/Options'; +import {babelLocationToRange, getRangeFirstCharacter} from './compiler/compat'; const SUPPORTED_LANGUAGE_IDS = new Set([ 'javascript', @@ -30,11 +36,21 @@ const connection = createConnection(ProposedFeatures.all); const documents = new TextDocuments(TextDocument); let compilerOptions: PluginOptions | null = null; -let lastResult: BabelFileResult | null = null; +let compiledFns: Set = new Set(); connection.onInitialize((_params: InitializeParams) => { // TODO(@poteto) get config fr - compilerOptions = resolveReactConfig('.'); + compilerOptions = resolveReactConfig('.') ?? defaultOptions; + compilerOptions = { + ...compilerOptions, + logger: { + logEvent(_filename: string | null, event: LoggerEvent) { + if (event.kind === 'CompileSuccess') { + compiledFns.add(event); + } + }, + }, + }; const result: InitializeResult = { capabilities: { textDocumentSync: TextDocumentSyncKind.Full, @@ -48,44 +64,58 @@ connection.onInitialized(() => { connection.console.log('initialized'); }); -documents.onDidOpen(async event => { - if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) { - const text = event.document.getText(); - const result = await compile({ - text, - file: event.document.uri, - options: compilerOptions, - }); - if (result.code != null) { - lastResult = result; - } - } -}); - documents.onDidChangeContent(async event => { + connection.console.info(`Changed: ${event.document.uri}`); + compiledFns.clear(); if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) { const text = event.document.getText(); - const result = await compile({ + await compile({ text, file: event.document.uri, options: compilerOptions, }); - if (result.code != null) { - lastResult = result; - } } }); connection.onDidChangeWatchedFiles(change => { + compiledFns.clear(); connection.console.log( change.changes.map(c => `File changed: ${c.uri}`).join('\n'), ); }); connection.onCodeLens(params => { - connection.console.log('lastResult: ' + JSON.stringify(lastResult, null, 2)); - connection.console.log('params: ' + JSON.stringify(params, null, 2)); - return []; + connection.console.info(`Handling codelens for: ${params.textDocument.uri}`); + if (compiledFns.size === 0) { + return; + } + const lenses: Array = []; + for (const compiled of compiledFns) { + if (compiled.fnLoc != null) { + const fnLoc = babelLocationToRange(compiled.fnLoc); + if (fnLoc === null) continue; + const lens = CodeLens.create( + getRangeFirstCharacter(fnLoc), + compiled.fnLoc, + ); + if (lastResult?.code != null) { + lens.command = { + title: 'Optimized by React Compiler', + command: 'todo', + }; + } + lenses.push(lens); + } + } + return lenses; +}); + +connection.onCodeLensResolve(lens => { + connection.console.info(`Resolving codelens for: ${JSON.stringify(lens)}`); + if (lastResult?.code != null) { + connection.console.log(lastResult.code); + } + return lens; }); documents.listen(connection); From ebc22ef7e15bf38dc91b7033782cedc2f43f7d6e Mon Sep 17 00:00:00 2001 From: lauren Date: Tue, 25 Feb 2025 19:09:21 -0500 Subject: [PATCH 62/73] [forgive][ez] Ignore test file (#32477) --- compiler/packages/react-forgive/.vscodeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/packages/react-forgive/.vscodeignore b/compiler/packages/react-forgive/.vscodeignore index 91f9572794cd5..9ef76302ed597 100644 --- a/compiler/packages/react-forgive/.vscodeignore +++ b/compiler/packages/react-forgive/.vscodeignore @@ -2,3 +2,4 @@ client server scripts +.vscode-test.mjs From 2df96224779237f532ca64c8c7e8a8605c06f067 Mon Sep 17 00:00:00 2001 From: lauren Date: Thu, 27 Feb 2025 11:24:30 -0500 Subject: [PATCH 63/73] [release] Update publishing scripts to make publishing allowlisted packages easier (#32486) It's getting unwieldy to list every single package to skip in these commands when you only want to publish one, ie eslint-plugin-react-hooks. This adds a new `onlyPackages` and `publishVersion` option to the publish commands to make that easier. --- .../parse-params.js | 7 +++++++ scripts/release/prepare-release-from-npm.js | 3 +++ scripts/release/publish-commands/parse-params.js | 12 ++++++++++++ scripts/release/publish.js | 12 +++++++++--- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/scripts/release/prepare-release-from-npm-commands/parse-params.js b/scripts/release/prepare-release-from-npm-commands/parse-params.js index b9aa1a4f5e7e8..b08d81e89dc18 100644 --- a/scripts/release/prepare-release-from-npm-commands/parse-params.js +++ b/scripts/release/prepare-release-from-npm-commands/parse-params.js @@ -13,6 +13,13 @@ const paramDefinitions = [ 'Skip NPM and use the build already present in "build/node_modules".', defaultValue: false, }, + { + name: 'onlyPackages', + type: String, + multiple: true, + description: 'Packages to include in publishing', + defaultValue: [], + }, { name: 'skipPackages', type: String, diff --git a/scripts/release/prepare-release-from-npm.js b/scripts/release/prepare-release-from-npm.js index e23ffb39bc1c0..8220fda220b65 100755 --- a/scripts/release/prepare-release-from-npm.js +++ b/scripts/release/prepare-release-from-npm.js @@ -28,6 +28,9 @@ const run = async () => { params.packages = await getPublicPackages(isExperimental); params.packages = params.packages.filter(packageName => { + if (params.onlyPackages.length > 0) { + return params.onlyPackages.includes(packageName); + } return !params.skipPackages.includes(packageName); }); diff --git a/scripts/release/publish-commands/parse-params.js b/scripts/release/publish-commands/parse-params.js index b7196447d4da8..ce9a9b7825c1a 100644 --- a/scripts/release/publish-commands/parse-params.js +++ b/scripts/release/publish-commands/parse-params.js @@ -19,6 +19,13 @@ const paramDefinitions = [ description: 'NPM tags to point to the new release.', defaultValue: ['untagged'], }, + { + name: 'onlyPackages', + type: String, + multiple: true, + description: 'Packages to include in publishing', + defaultValue: [], + }, { name: 'skipPackages', type: String, @@ -32,6 +39,11 @@ const paramDefinitions = [ description: 'Run in automated environment, without interactive prompts.', defaultValue: false, }, + { + name: 'publishVersion', + type: String, + description: 'Version to publish', + }, ]; module.exports = () => { diff --git a/scripts/release/publish.js b/scripts/release/publish.js index dcd31f1de7796..87b9940636b18 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -23,14 +23,20 @@ const run = async () => { try { const params = parseParams(); - const version = readJsonSync( - './build/node_modules/react/package.json' - ).version; + const version = + params.publishVersion ?? + readJsonSync('./build/node_modules/react/package.json').version; const isExperimental = version.includes('experimental'); params.cwd = join(__dirname, '..', '..'); params.packages = await getPublicPackages(isExperimental); + if (params.onlyPackages.length > 0) { + params.packages = params.packages.filter(packageName => { + return params.onlyPackages.includes(packageName); + }); + } + // Pre-filter any skipped packages to simplify the following commands. // As part of doing this we can also validate that none of the skipped packages were misspelled. params.skipPackages.forEach(packageName => { From 227e8414cc5af227b5de339cace2447d4a81c995 Mon Sep 17 00:00:00 2001 From: lauren Date: Thu, 27 Feb 2025 13:07:52 -0500 Subject: [PATCH 64/73] [ci] Add workflow to publish releases (#32487) Adds a new workflow to publish runtime releases from NPM. Note that I commented out the actual publish command so I can test it out first. --- .../runtime_releases_from_npm_manual.yml | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/workflows/runtime_releases_from_npm_manual.yml diff --git a/.github/workflows/runtime_releases_from_npm_manual.yml b/.github/workflows/runtime_releases_from_npm_manual.yml new file mode 100644 index 0000000000000..0851b38ff97b0 --- /dev/null +++ b/.github/workflows/runtime_releases_from_npm_manual.yml @@ -0,0 +1,91 @@ +name: (Runtime) Publish Releases from NPM Manual + +on: + workflow_dispatch: + inputs: + version_to_promote: + required: true + description: Current npm version (non-experimental) to promote + type: string + version_to_publish: + required: true + description: Version to publish for the specified packages + type: string + only_packages: + description: Space separated list of packages to publish on NPM. Use this OR skip_packages, not together. + type: string + skip_packages: + description: Space separated list of packages to NOT publish on NPM. Use this OR only_packages, not together. + type: string + tags: + description: Space separated list of tags to tag the release with on NPM + type: string + default: "['untagged']" + dry: + required: true + description: Don't actually publish, just run a dry run + type: boolean + default: true + force_notify: + description: Force a Discord notification + type: boolean + default: false + +env: + TZ: /usr/share/zoneinfo/America/Los_Angeles + # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 + GH_TOKEN: ${{ github.token }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + +jobs: + notify: + if: ${{ inputs.force_notify || inputs.dry == false || inputs.dry == 'false' }} + runs-on: ubuntu-latest + steps: + - name: Discord Webhook Action + uses: tsickert/discord-webhook@v6.0.0 + with: + webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} + embed-author-name: ${{ github.event.sender.login }} + embed-author-url: ${{ github.event.sender.html_url }} + embed-author-icon-url: ${{ github.event.sender.avatar_url }} + embed-title: '⚠️ Publishing release from NPM' + embed-description: | + ``` + inputs: ${{ toJson(inputs) }} + ``` + embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }} + + publish: + name: Publish releases + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: yarn + cache-dependency-path: yarn.lock + - name: Restore cached node_modules + uses: actions/cache@v4 + id: node_modules + with: + path: "**/node_modules" + key: runtime-release-node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'scripts/release/yarn.lock') }} + - name: Ensure clean build directory + run: rm -rf build + - run: yarn install --frozen-lockfile + - run: yarn install --frozen-lockfile + working-directory: scripts/release + - run: cp ./scripts/release/ci-npmrc ~/.npmrc + - if: '${{ inputs.only_packages }}' + run: | + scripts/release/prepare-release-from-npm.js --skipTests --version=${{ inputs.version_to_promote }} --onlyPackages=${{ inputs.only_packages }} + ls -R build/node_modules + # scripts/release/publish.js --ci --tags=${{ inputs.tags }} --publishVersion=${{ inputs.version_to_publish }} --onlyPackages=${{ inputs.only_packages }} --dry=${{ inputs.dry || 'false' }} + - if: '${{ inputs.skip_packages }}' + run: | + scripts/release/prepare-release-from-npm.js --skipTests --version=${{ inputs.version_to_promote }} --skipPackages=${{ inputs.skip_packages }} + ls -R build/node_modules + # scripts/release/publish.js --ci --tags=${{ inputs.tags }} --publishVersion=${{ inputs.version_to_publish }} --skipPackages=${{ inputs.skip_packages }} --dry=${{ inputs.dry || 'false' }} From 4c9392b43e9f39e17c18ef1c2cd0f0a14e85669c Mon Sep 17 00:00:00 2001 From: lauren Date: Thu, 27 Feb 2025 15:24:57 -0500 Subject: [PATCH 65/73] [ci] Prepare publish workflow (#32488) Fixes up a few things in the script and workflow to make it possible to run in CI without interactive prompts. --- .../runtime_releases_from_npm_manual.yml | 56 +++++++++++---- .../confirm-stable-version-numbers.js | 72 ++++++++++--------- .../guess-stable-version-numbers.js | 56 +++++++++------ .../parse-params.js | 11 +++ .../update-stable-version-numbers.js | 10 ++- scripts/release/prepare-release-from-npm.js | 7 ++ .../confirm-version-and-tags.js | 3 + .../publish-commands/publish-to-npm.js | 4 +- scripts/release/publish.js | 7 ++ 9 files changed, 154 insertions(+), 72 deletions(-) diff --git a/.github/workflows/runtime_releases_from_npm_manual.yml b/.github/workflows/runtime_releases_from_npm_manual.yml index 0851b38ff97b0..2759b4e4d8737 100644 --- a/.github/workflows/runtime_releases_from_npm_manual.yml +++ b/.github/workflows/runtime_releases_from_npm_manual.yml @@ -12,22 +12,22 @@ on: description: Version to publish for the specified packages type: string only_packages: - description: Space separated list of packages to publish on NPM. Use this OR skip_packages, not together. + description: Packages to publish (space separated) type: string skip_packages: - description: Space separated list of packages to NOT publish on NPM. Use this OR only_packages, not together. + description: Packages to NOT publish (space separated) type: string tags: - description: Space separated list of tags to tag the release with on NPM + description: NPM tags (space separated) type: string - default: "['untagged']" + default: untagged dry: required: true - description: Don't actually publish, just run a dry run + description: Dry run instead of publish? type: boolean default: true force_notify: - description: Force a Discord notification + description: Force a Discord notification? type: boolean default: false @@ -52,8 +52,8 @@ jobs: embed-author-icon-url: ${{ github.event.sender.avatar_url }} embed-title: '⚠️ Publishing release from NPM' embed-description: | - ``` - inputs: ${{ toJson(inputs) }} + ```json + ${{ toJson(inputs) }} ``` embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }} @@ -80,12 +80,44 @@ jobs: working-directory: scripts/release - run: cp ./scripts/release/ci-npmrc ~/.npmrc - if: '${{ inputs.only_packages }}' + name: 'Prepare and publish ${{ inputs.only_packages }}' run: | - scripts/release/prepare-release-from-npm.js --skipTests --version=${{ inputs.version_to_promote }} --onlyPackages=${{ inputs.only_packages }} + echo -e "===== Preparing release from NPM =====\n" + scripts/release/prepare-release-from-npm.js \ + --ci \ + --skipTests \ + --version=${{ inputs.version_to_promote }} \ + --publishVersion=${{ inputs.version_to_publish }} \ + --onlyPackages=${{ inputs.only_packages }} + + echo -e "\n\n===== Check prepared files =====\n" ls -R build/node_modules - # scripts/release/publish.js --ci --tags=${{ inputs.tags }} --publishVersion=${{ inputs.version_to_publish }} --onlyPackages=${{ inputs.only_packages }} --dry=${{ inputs.dry || 'false' }} + + echo -e "\n\n===== Publishing to NPM =====\n" + scripts/release/publish.js \ + --ci \ + --tags=${{ inputs.tags }} \ + --publishVersion=${{ inputs.version_to_publish }} \ + --onlyPackages=${{ inputs.only_packages }} \ + --dry=${{ inputs.dry }} - if: '${{ inputs.skip_packages }}' + name: 'Prepare and publish all packages EXCEPT ${{ inputs.skip_packages }}' run: | - scripts/release/prepare-release-from-npm.js --skipTests --version=${{ inputs.version_to_promote }} --skipPackages=${{ inputs.skip_packages }} + echo -e "===== Preparing release from NPM =====\n" + scripts/release/prepare-release-from-npm.js \ + --ci \ + --skipTests \ + --version=${{ inputs.version_to_promote }} \ + --publishVersion=${{ inputs.version_to_publish }} \ + --skipPackages=${{ inputs.skip_packages }} + + echo -e "\n\n===== Check prepared files =====\n" ls -R build/node_modules - # scripts/release/publish.js --ci --tags=${{ inputs.tags }} --publishVersion=${{ inputs.version_to_publish }} --skipPackages=${{ inputs.skip_packages }} --dry=${{ inputs.dry || 'false' }} + + echo -e "\n\n===== Publishing to NPM =====\n" + scripts/release/publish.js \ + --ci \ + --tags=${{ inputs.tags }} \ + --publishVersion=${{ inputs.version_to_publish }} \ + --skipPackages=${{ inputs.skip_packages }} \ + --dry=${{ inputs.dry }} diff --git a/scripts/release/prepare-release-from-npm-commands/confirm-stable-version-numbers.js b/scripts/release/prepare-release-from-npm-commands/confirm-stable-version-numbers.js index 0b93fba2fc12b..6bf49132149a8 100644 --- a/scripts/release/prepare-release-from-npm-commands/confirm-stable-version-numbers.js +++ b/scripts/release/prepare-release-from-npm-commands/confirm-stable-version-numbers.js @@ -7,7 +7,7 @@ const semver = require('semver'); const theme = require('../theme'); const {confirm} = require('../utils'); -const run = async ({skipPackages}, versionsMap) => { +const run = async ({ci, skipPackages}, versionsMap) => { const groupedVersionsMap = new Map(); // Group packages with the same source versions. @@ -22,44 +22,46 @@ const run = async ({skipPackages}, versionsMap) => { } }); - // Prompt user to confirm or override each version group. - const entries = [...groupedVersionsMap.entries()]; - for (let i = 0; i < entries.length; i++) { - const [bestGuessVersion, packages] = entries[i]; - const packageNames = packages.map(name => theme.package(name)).join(', '); + if (ci !== true) { + // Prompt user to confirm or override each version group if not running in CI. + const entries = [...groupedVersionsMap.entries()]; + for (let i = 0; i < entries.length; i++) { + const [bestGuessVersion, packages] = entries[i]; + const packageNames = packages.map(name => theme.package(name)).join(', '); - let version = bestGuessVersion; - if ( - skipPackages.some(skipPackageName => packages.includes(skipPackageName)) - ) { - await confirm( - theme`{spinnerSuccess ✓} Version for ${packageNames} will remain {version ${bestGuessVersion}}` - ); - } else { - const defaultVersion = bestGuessVersion - ? theme.version(` (default ${bestGuessVersion})`) - : ''; - version = - (await prompt( - theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: ` - )) || bestGuessVersion; - prompt.done(); - } + let version = bestGuessVersion; + if ( + skipPackages.some(skipPackageName => packages.includes(skipPackageName)) + ) { + await confirm( + theme`{spinnerSuccess ✓} Version for ${packageNames} will remain {version ${bestGuessVersion}}` + ); + } else { + const defaultVersion = bestGuessVersion + ? theme.version(` (default ${bestGuessVersion})`) + : ''; + version = + (await prompt( + theme`{spinnerSuccess ✓} Version for ${packageNames}${defaultVersion}: ` + )) || bestGuessVersion; + prompt.done(); + } - // Verify a valid version has been supplied. - try { - semver(version); + // Verify a valid version has been supplied. + try { + semver(version); - packages.forEach(packageName => { - versionsMap.set(packageName, version); - }); - } catch (error) { - console.log( - theme`{spinnerError ✘} Version {version ${version}} is invalid.` - ); + packages.forEach(packageName => { + versionsMap.set(packageName, version); + }); + } catch (error) { + console.log( + theme`{spinnerError ✘} Version {version ${version}} is invalid.` + ); - // Prompt again - i--; + // Prompt again + i--; + } } } }; diff --git a/scripts/release/prepare-release-from-npm-commands/guess-stable-version-numbers.js b/scripts/release/prepare-release-from-npm-commands/guess-stable-version-numbers.js index a3cde5d1c4fce..c0072b6637177 100644 --- a/scripts/release/prepare-release-from-npm-commands/guess-stable-version-numbers.js +++ b/scripts/release/prepare-release-from-npm-commands/guess-stable-version-numbers.js @@ -5,7 +5,10 @@ const semver = require('semver'); const {execRead, logPromise} = require('../utils'); -const run = async ({cwd, packages, skipPackages}, versionsMap) => { +const run = async ( + {cwd, packages, skipPackages, ci, publishVersion}, + versionsMap +) => { const branch = await execRead('git branch | grep \\* | cut -d " " -f2', { cwd, }); @@ -13,30 +16,41 @@ const run = async ({cwd, packages, skipPackages}, versionsMap) => { for (let i = 0; i < packages.length; i++) { const packageName = packages[i]; - try { - // In case local package JSONs are outdated, - // guess the next version based on the latest NPM release. - const version = await execRead(`npm show ${packageName} version`); - - if (skipPackages.includes(packageName)) { - versionsMap.set(packageName, version); + if (ci === true) { + if (publishVersion != null) { + versionsMap.set(packageName, publishVersion); } else { - const {major, minor, patch} = semver(version); - - // Guess the next version by incrementing patch. - // The script will confirm this later. - // By default, new releases from mains should increment the minor version number, - // and patch releases should be done from branches. - if (branch === 'main') { - versionsMap.set(packageName, `${major}.${minor + 1}.0`); + console.error( + 'When running in CI mode, a publishVersion must be supplied' + ); + process.exit(1); + } + } else { + try { + // In case local package JSONs are outdated, + // guess the next version based on the latest NPM release. + const version = await execRead(`npm show ${packageName} version`); + + if (skipPackages.includes(packageName)) { + versionsMap.set(packageName, version); } else { - versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`); + const {major, minor, patch} = semver(version); + + // Guess the next version by incrementing patch. + // The script will confirm this later. + // By default, new releases from mains should increment the minor version number, + // and patch releases should be done from branches. + if (branch === 'main') { + versionsMap.set(packageName, `${major}.${minor + 1}.0`); + } else { + versionsMap.set(packageName, `${major}.${minor}.${patch + 1}`); + } } + } catch (error) { + // If the package has not yet been published, + // we'll require a version number to be entered later. + versionsMap.set(packageName, null); } - } catch (error) { - // If the package has not yet been published, - // we'll require a version number to be entered later. - versionsMap.set(packageName, null); } } }; diff --git a/scripts/release/prepare-release-from-npm-commands/parse-params.js b/scripts/release/prepare-release-from-npm-commands/parse-params.js index b08d81e89dc18..ef9c4979b2030 100644 --- a/scripts/release/prepare-release-from-npm-commands/parse-params.js +++ b/scripts/release/prepare-release-from-npm-commands/parse-params.js @@ -39,6 +39,17 @@ const paramDefinitions = [ description: 'Version of published "next" release (e.g. 0.0.0-0e526bcec-20210202)', }, + { + name: 'publishVersion', + type: String, + description: 'Version to publish', + }, + { + name: 'ci', + type: Boolean, + description: 'Run in automated environment, without interactive prompts.', + defaultValue: false, + }, ]; module.exports = () => { diff --git a/scripts/release/prepare-release-from-npm-commands/update-stable-version-numbers.js b/scripts/release/prepare-release-from-npm-commands/update-stable-version-numbers.js index a41d83ac69dbb..f201118890456 100644 --- a/scripts/release/prepare-release-from-npm-commands/update-stable-version-numbers.js +++ b/scripts/release/prepare-release-from-npm-commands/update-stable-version-numbers.js @@ -9,7 +9,7 @@ const {join, relative} = require('path'); const {confirm, execRead, printDiff} = require('../utils'); const theme = require('../theme'); -const run = async ({cwd, packages, version}, versionsMap) => { +const run = async ({cwd, packages, version, ci}, versionsMap) => { const nodeModulesPath = join(cwd, 'build/node_modules'); // Cache all package JSONs for easy lookup below. @@ -107,7 +107,9 @@ const run = async ({cwd, packages, version}, versionsMap) => { printDependencies(packageJSON.dependencies, 'dependency'); printDependencies(packageJSON.peerDependencies, 'peer'); } - await confirm('Do the versions above look correct?'); + if (ci !== true) { + await confirm('Do the versions above look correct?'); + } clear(); @@ -167,7 +169,9 @@ const run = async ({cwd, packages, version}, versionsMap) => { console.log( theme`A full diff is available at {path ${relative(cwd, diffPath)}}.` ); - await confirm('Do the changes above look correct?'); + if (ci !== true) { + await confirm('Do the changes above look correct?'); + } } else { console.log( theme`Skipping React renderer version update because React is not included in the release.` diff --git a/scripts/release/prepare-release-from-npm.js b/scripts/release/prepare-release-from-npm.js index 8220fda220b65..bb67fddd37803 100755 --- a/scripts/release/prepare-release-from-npm.js +++ b/scripts/release/prepare-release-from-npm.js @@ -26,6 +26,13 @@ const run = async () => { params.version = await getLatestNextVersion(); } + if (params.onlyPackages.length > 0 && params.skipPackages.length > 0) { + console.error( + '--onlyPackages and --skipPackages cannot be used together' + ); + process.exit(1); + } + params.packages = await getPublicPackages(isExperimental); params.packages = params.packages.filter(packageName => { if (params.onlyPackages.length > 0) { diff --git a/scripts/release/publish-commands/confirm-version-and-tags.js b/scripts/release/publish-commands/confirm-version-and-tags.js index 6900878da02f6..2c6fed58d1937 100644 --- a/scripts/release/publish-commands/confirm-version-and-tags.js +++ b/scripts/release/publish-commands/confirm-version-and-tags.js @@ -38,6 +38,9 @@ const run = async ({cwd, packages, tags, ci}) => { console.log( theme`• {package ${packageName}} {version ${packageJSON.version}}` ); + if (ci) { + console.log(packageJSON); + } } if (!ci) { diff --git a/scripts/release/publish-commands/publish-to-npm.js b/scripts/release/publish-commands/publish-to-npm.js index d9e3b1902383f..f1e62657c0c38 100644 --- a/scripts/release/publish-commands/publish-to-npm.js +++ b/scripts/release/publish-commands/publish-to-npm.js @@ -27,7 +27,9 @@ const run = async ({cwd, dry, tags, ci}, packageName, otp) => { await confirm('Is this expected?'); } } else { - console.log(theme`{spinnerSuccess ✓} Publishing {package ${packageName}}`); + console.log( + theme`{spinnerSuccess ✓} Publishing {package ${packageName}}${dry ? ' (dry-run)' : ''}` + ); // Publish the package and tag it. if (!dry) { diff --git a/scripts/release/publish.js b/scripts/release/publish.js index 87b9940636b18..f9e450b559208 100755 --- a/scripts/release/publish.js +++ b/scripts/release/publish.js @@ -31,6 +31,13 @@ const run = async () => { params.cwd = join(__dirname, '..', '..'); params.packages = await getPublicPackages(isExperimental); + if (params.onlyPackages.length > 0 && params.skipPackages.length > 0) { + console.error( + '--onlyPackages and --skipPackages cannot be used together' + ); + process.exit(1); + } + if (params.onlyPackages.length > 0) { params.packages = params.packages.filter(packageName => { return params.onlyPackages.includes(packageName); From 5eb20b3007a8fafaf032c2e028c335ab09217d9f Mon Sep 17 00:00:00 2001 From: lauren Date: Thu, 27 Feb 2025 16:01:31 -0500 Subject: [PATCH 66/73] [ci] Fix --dry not being passed correctly (#32489) Boolean params for dry runs are true if the param exists at all, so only add it if we're in dry run mode. --- .../runtime_releases_from_npm_manual.yml | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/.github/workflows/runtime_releases_from_npm_manual.yml b/.github/workflows/runtime_releases_from_npm_manual.yml index 2759b4e4d8737..3c8cec7f7ac1b 100644 --- a/.github/workflows/runtime_releases_from_npm_manual.yml +++ b/.github/workflows/runtime_releases_from_npm_manual.yml @@ -50,7 +50,7 @@ jobs: embed-author-name: ${{ github.event.sender.login }} embed-author-url: ${{ github.event.sender.html_url }} embed-author-icon-url: ${{ github.event.sender.avatar_url }} - embed-title: '⚠️ Publishing release from NPM' + embed-title: "⚠️ Publishing release from NPM${{ inputs.dry && ' (dry run)' }}" embed-description: | ```json ${{ toJson(inputs) }} @@ -80,44 +80,40 @@ jobs: working-directory: scripts/release - run: cp ./scripts/release/ci-npmrc ~/.npmrc - if: '${{ inputs.only_packages }}' - name: 'Prepare and publish ${{ inputs.only_packages }}' + name: 'Prepare ${{ inputs.only_packages }} from NPM' run: | - echo -e "===== Preparing release from NPM =====\n" scripts/release/prepare-release-from-npm.js \ --ci \ --skipTests \ --version=${{ inputs.version_to_promote }} \ --publishVersion=${{ inputs.version_to_publish }} \ --onlyPackages=${{ inputs.only_packages }} - - echo -e "\n\n===== Check prepared files =====\n" - ls -R build/node_modules - - echo -e "\n\n===== Publishing to NPM =====\n" - scripts/release/publish.js \ - --ci \ - --tags=${{ inputs.tags }} \ - --publishVersion=${{ inputs.version_to_publish }} \ - --onlyPackages=${{ inputs.only_packages }} \ - --dry=${{ inputs.dry }} - if: '${{ inputs.skip_packages }}' - name: 'Prepare and publish all packages EXCEPT ${{ inputs.skip_packages }}' + name: 'Prepare all packages EXCEPT ${{ inputs.skip_packages }} from NPM' run: | - echo -e "===== Preparing release from NPM =====\n" scripts/release/prepare-release-from-npm.js \ --ci \ --skipTests \ --version=${{ inputs.version_to_promote }} \ --publishVersion=${{ inputs.version_to_publish }} \ --skipPackages=${{ inputs.skip_packages }} - - echo -e "\n\n===== Check prepared files =====\n" - ls -R build/node_modules - - echo -e "\n\n===== Publishing to NPM =====\n" + - name: Check prepared files + run: ls -R build/node_modules + - if: '${{ inputs.only_packages }}' + name: 'Publish ${{ inputs.only_packages }}' + run: | + scripts/release/publish.js \ + --ci \ + --tags=${{ inputs.tags }} \ + --publishVersion=${{ inputs.version_to_publish }} \ + --onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}} + ${{ inputs.dry && '--dry'}} + - if: '${{ inputs.skip_packages }}' + name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}' + run: | scripts/release/publish.js \ --ci \ --tags=${{ inputs.tags }} \ --publishVersion=${{ inputs.version_to_publish }} \ - --skipPackages=${{ inputs.skip_packages }} \ - --dry=${{ inputs.dry }} + --skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}} + ${{ inputs.dry && '--dry'}} From 3607f4838a8f4a87160da36aa26bb1432d7a5f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 27 Feb 2025 16:45:18 -0500 Subject: [PATCH 67/73] Add Commit Scaffolding for Gestures (#32451) This adds a `ReactFiberApplyGesture` which is basically intended to be a fork of the phases in `ReactFiberCommitWork` except for the fake commit that `useSwipeTransition` does. So far none of the phases are actually implemented yet. This is just the scaffolding around them so I can fill them in later. The important bit is that we call `startViewTransition` (via the `startGestureTransition` Config) when a gesture starts. We add a paused animation to prevent the transition from committing (even if the ScrollTimeline goes to 100%). This also locks the documents so that we can't commit any other Transitions until it completes. When the gesture completes (scroll end) then we stop the gesture View Transition. If there's no new work scheduled we do that immediately but if there was any new work already scheduled, then we assume that this will potentially commit the new state. So we wait for that to finish. This lets us lock the animation in its state instead of snapping back and then applying the real update. Using this technique we can't actually run a View Transition from the current state to the actual committed state because it would snap back to the beginning and then run the View Transition from there. Therefore any new commit needs to skip View Transitions even if it should've technically animated to that state. We assume that the new state is the same as the optimistic state you already swiped to. An alternative to this technique could be to commit the optimistic state when we cancel and then apply any new updates o top of that. I might explore that in the future. Regardless it's important that the `action` associated with the swipe schedules some work before we cancel. Otherwise it risks reverting first. So I had to update this in the fixture. --- .../src/components/SwipeRecognizer.js | 10 +- packages/react-art/src/ReactFiberConfigART.js | 6 + .../src/client/ReactFiberConfigDOM.js | 74 ++++++- .../src/ReactFiberConfigNative.js | 15 ++ .../src/createReactNoop.js | 15 ++ .../src/ReactFiberApplyGesture.js | 42 ++++ .../src/ReactFiberConfigWithNoMutation.js | 3 + .../src/ReactFiberGestureScheduler.js | 80 ++++++-- .../react-reconciler/src/ReactFiberHooks.js | 2 +- .../react-reconciler/src/ReactFiberRoot.js | 3 +- .../src/ReactFiberWorkLoop.js | 180 +++++++++++++++--- .../src/ReactInternalTypes.js | 3 +- .../src/forks/ReactFiberConfig.custom.js | 3 + .../src/ReactFiberConfigTestHost.js | 15 ++ 14 files changed, 401 insertions(+), 50 deletions(-) create mode 100644 packages/react-reconciler/src/ReactFiberApplyGesture.js diff --git a/fixtures/view-transition/src/components/SwipeRecognizer.js b/fixtures/view-transition/src/components/SwipeRecognizer.js index f332796fc8cdf..8e913dfb4e55f 100644 --- a/fixtures/view-transition/src/components/SwipeRecognizer.js +++ b/fixtures/view-transition/src/components/SwipeRecognizer.js @@ -33,11 +33,6 @@ export default function SwipeRecognizer({ }); } function onScrollEnd() { - if (activeGesture.current !== null) { - const cancelGesture = activeGesture.current; - activeGesture.current = null; - cancelGesture(); - } let changed; const scrollElement = scrollRef.current; if (axis === 'x') { @@ -60,6 +55,11 @@ export default function SwipeRecognizer({ // Trigger side-effects startTransition(action); } + if (activeGesture.current !== null) { + const cancelGesture = activeGesture.current; + activeGesture.current = null; + cancelGesture(); + } } useEffect(() => { diff --git a/packages/react-art/src/ReactFiberConfigART.js b/packages/react-art/src/ReactFiberConfigART.js index a011bdf5c71c7..dc746b0e17d88 100644 --- a/packages/react-art/src/ReactFiberConfigART.js +++ b/packages/react-art/src/ReactFiberConfigART.js @@ -500,6 +500,12 @@ export function startViewTransition() { return false; } +export type RunningGestureTransition = null; + +export function startGestureTransition() {} + +export function stopGestureTransition(transition: RunningGestureTransition) {} + export type ViewTransitionInstance = null | {name: string, ...}; export function createViewTransitionInstance( diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 52560d0b006b0..2b288efe46c3f 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -1394,7 +1394,10 @@ export function startViewTransition( transition.ready.then(spawnedWorkCallback, spawnedWorkCallback); transition.finished.then(() => { // $FlowFixMe[prop-missing] - ownerDocument.__reactViewTransition = null; + if (ownerDocument.__reactViewTransition === transition) { + // $FlowFixMe[prop-missing] + ownerDocument.__reactViewTransition = null; + } passiveCallback(); }); return true; @@ -1409,6 +1412,75 @@ export function startViewTransition( } } +export type RunningGestureTransition = { + skipTransition(): void, + ... +}; + +export function startGestureTransition( + rootContainer: Container, + transitionTypes: null | TransitionTypes, + mutationCallback: () => void, + animateCallback: () => void, +): null | RunningGestureTransition { + const ownerDocument: Document = + rootContainer.nodeType === DOCUMENT_NODE + ? (rootContainer: any) + : rootContainer.ownerDocument; + try { + // $FlowFixMe[prop-missing] + const transition = ownerDocument.startViewTransition({ + update: mutationCallback, + types: transitionTypes, + }); + // $FlowFixMe[prop-missing] + ownerDocument.__reactViewTransition = transition; + let blockingAnim = null; + const readyCallback = () => { + // View Transitions with ScrollTimeline has a quirk where they end if the + // ScrollTimeline ever reaches 100% but that doesn't mean we're done because + // you can swipe back again. We can prevent this by adding a paused Animation + // that never stops. This seems to keep all running Animations alive until + // we explicitly abort (or something forces the View Transition to cancel). + const documentElement: Element = (ownerDocument.documentElement: any); + blockingAnim = documentElement.animate([{}, {}], { + pseudoElement: '::view-transition', + duration: 1, + }); + blockingAnim.pause(); + animateCallback(); + }; + transition.ready.then(readyCallback, readyCallback); + transition.finished.then(() => { + if (blockingAnim !== null) { + // In Safari, we need to manually clear this or it'll block future transitions. + blockingAnim.cancel(); + } + // $FlowFixMe[prop-missing] + if (ownerDocument.__reactViewTransition === transition) { + // $FlowFixMe[prop-missing] + ownerDocument.__reactViewTransition = null; + } + }); + return transition; + } catch (x) { + // We use the error as feature detection. + // The only thing that should throw is if startViewTransition is missing + // or if it doesn't accept the object form. Other errors are async. + // I.e. it's before the View Transitions v2 spec. We only support View + // Transitions v2 otherwise we fallback to not animating to ensure that + // we're not animating with the wrong animation mapped. + // Run through the sequence to put state back into a consistent state. + mutationCallback(); + animateCallback(); + return null; + } +} + +export function stopGestureTransition(transition: RunningGestureTransition) { + transition.skipTransition(); +} + interface ViewTransitionPseudoElementType extends Animatable { _scope: HTMLElement; _selector: string; diff --git a/packages/react-native-renderer/src/ReactFiberConfigNative.js b/packages/react-native-renderer/src/ReactFiberConfigNative.js index 00385547f23e7..4a9064c82a83b 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigNative.js +++ b/packages/react-native-renderer/src/ReactFiberConfigNative.js @@ -597,6 +597,21 @@ export function startViewTransition( return false; } +export type RunningGestureTransition = null; + +export function startGestureTransition( + rootContainer: Container, + transitionTypes: null | TransitionTypes, + mutationCallback: () => void, + animateCallback: () => void, +): RunningGestureTransition { + mutationCallback(); + animateCallback(); + return null; +} + +export function stopGestureTransition(transition: RunningGestureTransition) {} + export type ViewTransitionInstance = null | {name: string, ...}; export function createViewTransitionInstance( diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 43853a864fb5e..e392ad7b0c04f 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -93,6 +93,8 @@ export type TransitionStatus = mixed; export type FormInstance = Instance; +export type RunningGestureTransition = null; + export type ViewTransitionInstance = null | {name: string, ...}; export type GestureTimeline = null; @@ -792,6 +794,19 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { return false; }, + startGestureTransition( + rootContainer: Container, + transitionTypes: null | TransitionTypes, + mutationCallback: () => void, + animateCallback: () => void, + ): RunningGestureTransition { + mutationCallback(); + animateCallback(); + return null; + }, + + stopGestureTransition(transition: RunningGestureTransition) {}, + createViewTransitionInstance(name: string): ViewTransitionInstance { return null; }, diff --git a/packages/react-reconciler/src/ReactFiberApplyGesture.js b/packages/react-reconciler/src/ReactFiberApplyGesture.js new file mode 100644 index 0000000000000..46af2ca4309fa --- /dev/null +++ b/packages/react-reconciler/src/ReactFiberApplyGesture.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Fiber, FiberRoot} from './ReactInternalTypes'; + +import { + cancelRootViewTransitionName, + restoreRootViewTransitionName, +} from './ReactFiberConfig'; + +// Clone View Transition boundaries that have any mutations or might have had their +// layout affected by child insertions. +export function insertDestinationClones( + root: FiberRoot, + finishedWork: Fiber, +): void { + // TODO +} + +// Revert insertions and apply view transition names to the "new" (current) state. +export function applyDepartureTransitions( + root: FiberRoot, + finishedWork: Fiber, +): void { + // TODO + cancelRootViewTransitionName(root.containerInfo); +} + +// Revert transition names and start/adjust animations on the started View Transition. +export function startGestureAnimations( + root: FiberRoot, + finishedWork: Fiber, +): void { + // TODO + restoreRootViewTransitionName(root.containerInfo); +} diff --git a/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js b/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js index da34b26084de5..430ad02abc1b1 100644 --- a/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js +++ b/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js @@ -46,6 +46,9 @@ export const wasInstanceInViewport = shim; export const hasInstanceChanged = shim; export const hasInstanceAffectedParent = shim; export const startViewTransition = shim; +export type RunningGestureTransition = null; +export const startGestureTransition = shim; +export const stopGestureTransition = shim; export type ViewTransitionInstance = null | {name: string, ...}; export const createViewTransitionInstance = shim; export type GestureTimeline = any; diff --git a/packages/react-reconciler/src/ReactFiberGestureScheduler.js b/packages/react-reconciler/src/ReactFiberGestureScheduler.js index fffb7d524d9e4..33d477f07daec 100644 --- a/packages/react-reconciler/src/ReactFiberGestureScheduler.js +++ b/packages/react-reconciler/src/ReactFiberGestureScheduler.js @@ -8,11 +8,21 @@ */ import type {FiberRoot} from './ReactInternalTypes'; -import type {GestureTimeline} from './ReactFiberConfig'; +import type { + GestureTimeline, + RunningGestureTransition, +} from './ReactFiberConfig'; -import {GestureLane} from './ReactFiberLane'; +import { + GestureLane, + includesBlockingLane, + includesTransitionLane, +} from './ReactFiberLane'; import {ensureRootIsScheduled} from './ReactFiberRootScheduler'; -import {subscribeToGestureDirection} from './ReactFiberConfig'; +import { + subscribeToGestureDirection, + stopGestureTransition, +} from './ReactFiberConfig'; // This type keeps track of any scheduled or active gestures. export type ScheduledGesture = { @@ -23,6 +33,7 @@ export type ScheduledGesture = { rangeCurrent: number, // The starting offset along the timeline. rangeNext: number, // The end along the timeline where the next state is reached. cancel: () => void, // Cancel the subscription to direction change. + running: null | RunningGestureTransition, // Used to cancel the running transition after we're done. prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root. next: null | ScheduledGesture, // The next scheduled gesture in the queue for this root. }; @@ -35,7 +46,7 @@ export function scheduleGesture( rangeCurrent: number, rangeNext: number, ): ScheduledGesture { - let prev = root.gestures; + let prev = root.pendingGestures; while (prev !== null) { if (prev.provider === provider) { // Existing instance found. @@ -59,16 +70,16 @@ export function scheduleGesture( } if (gesture.direction !== direction) { gesture.direction = direction; - if (gesture.prev === null && root.gestures !== gesture) { + if (gesture.prev === null && root.pendingGestures !== gesture) { // This gesture is not in the schedule, meaning it was already rendered. // We need to rerender in the new direction. Insert it into the first slot // in case other gestures are queued after the on-going one. - const existing = root.gestures; + const existing = root.pendingGestures; gesture.next = existing; if (existing !== null) { existing.prev = gesture; } - root.gestures = gesture; + root.pendingGestures = gesture; // Schedule the lane on the root. The Fibers will already be marked as // long as the gesture is active on that Hook. root.pendingLanes |= GestureLane; @@ -86,11 +97,12 @@ export function scheduleGesture( rangeCurrent: rangeCurrent, rangeNext: rangeNext, cancel: cancel, + running: null, prev: prev, next: null, }; if (prev === null) { - root.gestures = gesture; + root.pendingGestures = gesture; } else { prev.next = gesture; } @@ -106,10 +118,35 @@ export function cancelScheduledGesture( if (gesture.count === 0) { const cancelDirectionSubscription = gesture.cancel; cancelDirectionSubscription(); - // Delete the scheduled gesture from the queue. + // Delete the scheduled gesture from the pending queue. deleteScheduledGesture(root, gesture); // TODO: If we're currently rendering this gesture, we need to restart the render // on a different gesture or cancel the render.. + // TODO: We might want to pause the View Transition at this point since you should + // no longer be able to update the position of anything but it might be better to + // just commit the gesture state. + const runningTransition = gesture.running; + if (runningTransition !== null) { + const pendingLanesExcludingGestureLane = root.pendingLanes & ~GestureLane; + if ( + includesBlockingLane(pendingLanesExcludingGestureLane) || + includesTransitionLane(pendingLanesExcludingGestureLane) + ) { + // If we have pending work we schedule the gesture to be stopped at the next commit. + // This ensures that we don't snap back to the previous state until we have + // had a chance to commit any resulting updates. + const existing = root.stoppingGestures; + if (existing !== null) { + gesture.next = existing; + existing.prev = gesture; + } + root.stoppingGestures = gesture; + } else { + gesture.running = null; + // If there's no work scheduled so we can stop the View Transition right away. + stopGestureTransition(runningTransition); + } + } } } @@ -118,15 +155,19 @@ export function deleteScheduledGesture( gesture: ScheduledGesture, ): void { if (gesture.prev === null) { - if (root.gestures === gesture) { - root.gestures = gesture.next; - if (root.gestures === null) { + if (root.pendingGestures === gesture) { + root.pendingGestures = gesture.next; + if (root.pendingGestures === null) { // Gestures don't clear their lanes while the gesture is still active but it // might not be scheduled to do any more renders and so we shouldn't schedule // any more gesture lane work until a new gesture is scheduled. root.pendingLanes &= ~GestureLane; } } + if (root.stoppingGestures === gesture) { + // This should not really happen the way we use it now but just in case we start. + root.stoppingGestures = gesture.next; + } } else { gesture.prev.next = gesture.next; if (gesture.next !== null) { @@ -136,3 +177,18 @@ export function deleteScheduledGesture( gesture.next = null; } } + +export function stopCompletedGestures(root: FiberRoot) { + let gesture = root.stoppingGestures; + root.stoppingGestures = null; + while (gesture !== null) { + if (gesture.running !== null) { + stopGestureTransition(gesture.running); + gesture.running = null; + } + const nextGesture = gesture.next; + gesture.next = null; + gesture.prev = null; + gesture = nextGesture; + } +} diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 50424e9d7b4db..270cd87d4941c 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -4126,7 +4126,7 @@ function updateSwipeTransition( ); } // We assume that the currently rendering gesture is the one first in the queue. - const rootRenderGesture = root.gestures; + const rootRenderGesture = root.pendingGestures; if (rootRenderGesture !== null) { let update = queue.pending; while (update !== null) { diff --git a/packages/react-reconciler/src/ReactFiberRoot.js b/packages/react-reconciler/src/ReactFiberRoot.js index 03ddde7a5ab5b..41401bac2bd8f 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.js +++ b/packages/react-reconciler/src/ReactFiberRoot.js @@ -99,7 +99,8 @@ function FiberRootNode( this.formState = formState; if (enableSwipeTransition) { - this.gestures = null; + this.pendingGestures = null; + this.stoppingGestures = null; } this.incompleteTransitions = new Map(); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index d5ef003e3dc61..bf611f73275a7 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -100,6 +100,7 @@ import { resolveUpdatePriority, trackSchedulerEvent, startViewTransition, + startGestureTransition, createViewTransitionInstance, } from './ReactFiberConfig'; @@ -228,6 +229,11 @@ import { accumulateSuspenseyCommit, } from './ReactFiberCommitWork'; import {shouldStartViewTransition} from './ReactFiberCommitViewTransitions'; +import { + insertDestinationClones, + applyDepartureTransitions, + startGestureAnimations, +} from './ReactFiberApplyGesture'; import {enqueueUpdate} from './ReactFiberClassUpdateQueue'; import {resetContextDependencies} from './ReactFiberNewContext'; import { @@ -341,7 +347,10 @@ import { import {getMaskedContext, getUnmaskedContext} from './ReactFiberContext'; import {peekEntangledActionLane} from './ReactFiberAsyncAction'; import {logUncaughtError} from './ReactFiberErrorLogger'; -import {deleteScheduledGesture} from './ReactFiberGestureScheduler'; +import { + deleteScheduledGesture, + stopCompletedGestures, +} from './ReactFiberGestureScheduler'; const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; @@ -644,7 +653,9 @@ const PENDING_LAYOUT_PHASE = 2; const PENDING_AFTER_MUTATION_PHASE = 3; const PENDING_SPAWNED_WORK = 4; const PENDING_PASSIVE_PHASE = 5; -let pendingEffectsStatus: 0 | 1 | 2 | 3 | 4 | 5 = 0; +const PENDING_GESTURE_MUTATION_PHASE = 6; +const PENDING_GESTURE_ANIMATION_PHASE = 7; +let pendingEffectsStatus: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0; let pendingEffectsRoot: FiberRoot = (null: any); let pendingFinishedWork: Fiber = (null: any); let pendingEffectsLanes: Lanes = NoLanes; @@ -1424,11 +1435,12 @@ function commitRootWhenReady( const subtreeFlags = finishedWork.subtreeFlags; const isViewTransitionEligible = enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes); // TODO: Use a subtreeFlag to optimize. + const isGestureTransition = enableSwipeTransition && isGestureRender(lanes); const maySuspendCommit = subtreeFlags & ShouldSuspendCommit || (subtreeFlags & BothVisibilityAndMaySuspendCommit) === BothVisibilityAndMaySuspendCommit; - if (isViewTransitionEligible || maySuspendCommit) { + if (isViewTransitionEligible || maySuspendCommit || isGestureTransition) { // Before committing, ask the renderer whether the host tree is ready. // If it's not, we'll wait until it notifies us. startSuspendingCommit(); @@ -1439,8 +1451,12 @@ function commitRootWhenReady( // This will also track any newly added or appearing ViewTransition // components for the purposes of forming pairs. accumulateSuspenseyCommit(finishedWork); - if (isViewTransitionEligible) { - suspendOnActiveViewTransition(root.containerInfo); + if (isViewTransitionEligible || isGestureTransition) { + // If we're stopping gestures we don't have to wait for any pending + // view transition. We'll stop it when we commit. + if (!enableSwipeTransition || root.stoppingGestures === null) { + suspendOnActiveViewTransition(root.containerInfo); + } } // At the end, ask the renderer if it's ready to commit, or if we should // suspend. If it's not ready, it will return a callback to subscribe to @@ -3263,6 +3279,12 @@ function commitRoot( if (enableSchedulingProfiler) { markCommitStopped(); } + if (enableSwipeTransition) { + // Stop any gestures that were completed and is now being reverted. + if (root.stoppingGestures !== null) { + stopCompletedGestures(root); + } + } return; } else { if (__DEV__) { @@ -3291,7 +3313,7 @@ function commitRoot( const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes(); remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes); - if (enableSwipeTransition && root.gestures === null) { + if (enableSwipeTransition && root.pendingGestures === null) { // Gestures don't clear their lanes while the gesture is still active but it // might not be scheduled to do any more renders and so we shouldn't schedule // any more gesture lane work until a new gesture is scheduled. @@ -3321,21 +3343,6 @@ function commitRoot( // times out. } - if (enableSwipeTransition && isGestureRender(lanes)) { - // This is a special kind of render that doesn't commit regular effects. - commitGestureOnRoot( - root, - finishedWork, - recoverableErrors, - enableProfilerTimer - ? suspendedCommitReason === IMMEDIATE_COMMIT - ? completedRenderEndTime - : commitStartTime - : 0, - ); - return; - } - // workInProgressX might be overwritten, so we want // to store it in pendingPassiveX until they get processed // We need to pass this through as an argument to commitRoot @@ -3354,6 +3361,21 @@ function commitRoot( pendingSuspendedCommitReason = suspendedCommitReason; } + if (enableSwipeTransition && isGestureRender(lanes)) { + // This is a special kind of render that doesn't commit regular effects. + commitGestureOnRoot( + root, + finishedWork, + recoverableErrors, + enableProfilerTimer + ? suspendedCommitReason === IMMEDIATE_COMMIT + ? completedRenderEndTime + : commitStartTime + : 0, + ); + return; + } + // If there are pending passive effects, schedule a callback to process them. // Do this as early as possible, so it is queued before anything else that // might get scheduled in the commit phase. (See #16714.) @@ -3461,10 +3483,23 @@ function commitRoot( ReactSharedInternals.T = prevTransition; } } + + let willStartViewTransition = shouldStartViewTransition; + if (enableSwipeTransition) { + // Stop any gestures that were completed and is now being committed. + if (root.stoppingGestures !== null) { + stopCompletedGestures(root); + // If we are in the process of stopping some gesture we shouldn't start + // a View Transition because that would start from the previous state to + // the next state. + willStartViewTransition = false; + } + } + pendingEffectsStatus = PENDING_MUTATION_PHASE; const startedViewTransition = enableViewTransition && - shouldStartViewTransition && + willStartViewTransition && startViewTransition( root.containerInfo, pendingTransitionTypes, @@ -3633,6 +3668,7 @@ function flushSpawnedWork(): void { } else { pendingEffectsStatus = NO_PENDING_EFFECTS; pendingEffectsRoot = (null: any); // Clear for GC purposes. + pendingFinishedWork = (null: any); // Clear for GC purposes. // There were no passive effects, so we can immediately release the cache // pool for this render. releaseRootPooledCache(root, root.pendingLanes); @@ -3830,20 +3866,103 @@ function flushSpawnedWork(): void { function commitGestureOnRoot( root: FiberRoot, - finishedWork: null | Fiber, + finishedWork: Fiber, recoverableErrors: null | Array>, renderEndTime: number, // Profiling-only ): void { // We assume that the gesture we just rendered was the first one in the queue. - const finishedGesture = root.gestures; + const finishedGesture = root.pendingGestures; if (finishedGesture === null) { - throw new Error( - 'Finished rendering the gesture lane but there were no pending gestures. ' + - 'React should not have started a render in this case. This is a bug in React.', - ); + // We must have already cancelled this gesture before we had a chance to + // render it. Let's schedule work on the next set of lanes. + ensureRootIsScheduled(root); + return; } deleteScheduledGesture(root, finishedGesture); - // TODO: Run the gesture + + const prevTransition = ReactSharedInternals.T; + ReactSharedInternals.T = null; + const previousPriority = getCurrentUpdatePriority(); + setCurrentUpdatePriority(DiscreteEventPriority); + const prevExecutionContext = executionContext; + executionContext |= CommitContext; + try { + insertDestinationClones(root, finishedWork); + } finally { + // Reset the priority to the previous non-sync value. + executionContext = prevExecutionContext; + setCurrentUpdatePriority(previousPriority); + ReactSharedInternals.T = prevTransition; + } + // TODO: Collect transition types. + pendingTransitionTypes = null; + pendingEffectsStatus = PENDING_GESTURE_MUTATION_PHASE; + + finishedGesture.running = startGestureTransition( + root.containerInfo, + pendingTransitionTypes, + flushGestureMutations, + flushGestureAnimations, + ); +} + +function flushGestureMutations(): void { + if (pendingEffectsStatus !== PENDING_GESTURE_MUTATION_PHASE) { + return; + } + pendingEffectsStatus = NO_PENDING_EFFECTS; + const root = pendingEffectsRoot; + const finishedWork = pendingFinishedWork; + + const prevTransition = ReactSharedInternals.T; + ReactSharedInternals.T = null; + const previousPriority = getCurrentUpdatePriority(); + setCurrentUpdatePriority(DiscreteEventPriority); + const prevExecutionContext = executionContext; + executionContext |= CommitContext; + try { + applyDepartureTransitions(root, finishedWork); + } finally { + // Reset the priority to the previous non-sync value. + executionContext = prevExecutionContext; + setCurrentUpdatePriority(previousPriority); + ReactSharedInternals.T = prevTransition; + } + + pendingEffectsStatus = PENDING_GESTURE_ANIMATION_PHASE; +} + +function flushGestureAnimations(): void { + // If we get canceled before we start we might not have applied + // mutations yet. We need to apply them first. + flushGestureMutations(); + if (pendingEffectsStatus !== PENDING_GESTURE_ANIMATION_PHASE) { + return; + } + pendingEffectsStatus = NO_PENDING_EFFECTS; + const root = pendingEffectsRoot; + const finishedWork = pendingFinishedWork; + pendingEffectsRoot = (null: any); // Clear for GC purposes. + pendingFinishedWork = (null: any); // Clear for GC purposes. + pendingEffectsLanes = NoLanes; + + const prevTransition = ReactSharedInternals.T; + ReactSharedInternals.T = null; + const previousPriority = getCurrentUpdatePriority(); + setCurrentUpdatePriority(DiscreteEventPriority); + const prevExecutionContext = executionContext; + executionContext |= CommitContext; + try { + startGestureAnimations(root, finishedWork); + } finally { + // Reset the priority to the previous non-sync value. + executionContext = prevExecutionContext; + setCurrentUpdatePriority(previousPriority); + ReactSharedInternals.T = prevTransition; + } + + // Now that we've rendered this lane. Start working on the next lane. + ensureRootIsScheduled(root); } function makeErrorInfo(componentStack: ?string) { @@ -3879,6 +3998,8 @@ function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) { export function flushPendingEffects(wasDelayedCommit?: boolean): boolean { // Returns whether passive effects were flushed. + flushGestureMutations(); + flushGestureAnimations(); flushMutationEffects(); flushLayoutEffects(); // Skip flushAfterMutation if we're forcing this early. @@ -3932,6 +4053,7 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) { const lanes = pendingEffectsLanes; pendingEffectsStatus = NO_PENDING_EFFECTS; pendingEffectsRoot = (null: any); // Clear for GC purposes. + pendingFinishedWork = (null: any); // Clear for GC purposes. // TODO: This is sometimes out of sync with pendingEffectsRoot. // Figure out why and fix it. It's not causing any known issues (probably // because it's only used for profiling), but it's a refactor hazard. diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index 3479eba1b9dd3..be5e9a5c0da69 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -284,7 +284,8 @@ type BaseFiberRootProperties = { formState: ReactFormState | null, // enableSwipeTransition only - gestures: null | ScheduledGesture, + pendingGestures: null | ScheduledGesture, + stoppingGestures: null | ScheduledGesture, }; // The following attributes are only used by DevTools and are only present in DEV builds. diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js index 6b2781c0ab2a5..9569b4a896cee 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js @@ -40,6 +40,7 @@ export opaque type NoTimeout = mixed; export opaque type RendererInspectionConfig = mixed; export opaque type TransitionStatus = mixed; export opaque type FormInstance = mixed; +export type RunningGestureTransition = mixed; export type ViewTransitionInstance = null | {name: string, ...}; export opaque type InstanceMeasurement = mixed; export type EventResponder = any; @@ -145,6 +146,8 @@ export const wasInstanceInViewport = $$$config.wasInstanceInViewport; export const hasInstanceChanged = $$$config.hasInstanceChanged; export const hasInstanceAffectedParent = $$$config.hasInstanceAffectedParent; export const startViewTransition = $$$config.startViewTransition; +export const startGestureTransition = $$$config.startGestureTransition; +export const stopGestureTransition = $$$config.stopGestureTransition; export const getCurrentGestureOffset = $$$config.getCurrentGestureOffset; export const subscribeToGestureDirection = $$$config.subscribeToGestureDirection; diff --git a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js index 8f4f81cabb9ce..48939e9ff45ad 100644 --- a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js +++ b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js @@ -375,6 +375,21 @@ export function startViewTransition( return false; } +export type RunningGestureTransition = null; + +export function startGestureTransition( + rootContainer: Container, + transitionTypes: null | TransitionTypes, + mutationCallback: () => void, + animateCallback: () => void, +): RunningGestureTransition { + mutationCallback(); + animateCallback(); + return null; +} + +export function stopGestureTransition(transition: RunningGestureTransition) {} + export type ViewTransitionInstance = null | {name: string, ...}; export function createViewTransitionInstance( From 7e2ea902f839264fd327b0df5fae4f6ad8359952 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 28 Feb 2025 10:58:28 -0500 Subject: [PATCH 68/73] [ci] Fix discord notification title (#32491) fun times --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32491). * #32492 * __->__ #32491 --- .github/workflows/runtime_releases_from_npm_manual.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/runtime_releases_from_npm_manual.yml b/.github/workflows/runtime_releases_from_npm_manual.yml index 3c8cec7f7ac1b..2e1047b55df50 100644 --- a/.github/workflows/runtime_releases_from_npm_manual.yml +++ b/.github/workflows/runtime_releases_from_npm_manual.yml @@ -50,7 +50,7 @@ jobs: embed-author-name: ${{ github.event.sender.login }} embed-author-url: ${{ github.event.sender.html_url }} embed-author-icon-url: ${{ github.event.sender.avatar_url }} - embed-title: "⚠️ Publishing release from NPM${{ inputs.dry && ' (dry run)' }}" + embed-title: "⚠️ Publishing release from NPM${{ (inputs.dry && ' (dry run)') || '' }}" embed-description: | ```json ${{ toJson(inputs) }} From 56c7d1070aabe42b955ea0006477ea1ca02dd0c5 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 28 Feb 2025 10:58:39 -0500 Subject: [PATCH 69/73] [ci] Upload release for easier debugging (#32492) Uploads the releases that were published in case to help with debugging or verifying a dry run. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32492). * __->__ #32492 * #32491 --- .github/workflows/runtime_releases_from_npm_manual.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/runtime_releases_from_npm_manual.yml b/.github/workflows/runtime_releases_from_npm_manual.yml index 2e1047b55df50..c0b3867e03da0 100644 --- a/.github/workflows/runtime_releases_from_npm_manual.yml +++ b/.github/workflows/runtime_releases_from_npm_manual.yml @@ -117,3 +117,9 @@ jobs: --publishVersion=${{ inputs.version_to_publish }} \ --skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}} ${{ inputs.dry && '--dry'}} + - name: Archive released package for debugging + uses: actions/upload-artifact@v4 + with: + name: build + path: | + ./build/node_modules From 11ca4f6b6934292168f49cc78d3b2360a602febe Mon Sep 17 00:00:00 2001 From: michael faith Date: Fri, 28 Feb 2025 10:07:13 -0600 Subject: [PATCH 70/73] feat(eslint-plugin-react-hooks): update `engines` declaration (#32458) In preparation for the merging of the compiler plugin into this one (#32416), this change proactively updates the plugin's `engines` declaration to require Node versions greater than or equal to 18 BREAKING CHANGE Co-authored-by: lauren --- packages/eslint-plugin-react-hooks/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index d3717b9dee61c..48cb7ae452a8d 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -31,7 +31,7 @@ "main": "./index.js", "types": "./index.d.ts", "engines": { - "node": ">=10" + "node": ">=18" }, "homepage": "https://react.dev/", "peerDependencies": { From d55cc79bcf5cd3bd4cb406381b067d72842f368e Mon Sep 17 00:00:00 2001 From: michael faith Date: Fri, 28 Feb 2025 10:12:10 -0600 Subject: [PATCH 71/73] refactor(eslint-plugin-react-hooks): move rules to `rules` folder (#32411) Since the compiler plugin is going to be merged into the hooks plugin, and ultimately decomposed into several more rules, it would be good to start creating a more traditional folder structure for the plugin. This change just moves the rules into a `rules` folder. Co-authored-by: lauren --- packages/eslint-plugin-react-hooks/README.md | 4 ++-- packages/eslint-plugin-react-hooks/src/index.ts | 4 ++-- .../src/{ => rules}/ExhaustiveDeps.ts | 0 .../eslint-plugin-react-hooks/src/{ => rules}/RulesOfHooks.ts | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename packages/eslint-plugin-react-hooks/src/{ => rules}/ExhaustiveDeps.ts (100%) rename packages/eslint-plugin-react-hooks/src/{ => rules}/RulesOfHooks.ts (100%) diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index 36f63722f7268..cf54caec986f5 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -36,7 +36,7 @@ If you are still using ESLint below 9.0.0, please continue to use `recommended-l For [ESLint 9.0.0 and above](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/) users, add the `recommended-latest` config. ```js -import reactHooks from 'eslint-plugin-react-hooks'; +import * as reactHooks from 'eslint-plugin-react-hooks'; export default [ // ... @@ -67,7 +67,7 @@ If you want more fine-grained configuration, you can instead add a snippet like #### Flat Config (eslint.config.js) ```js -import reactHooks from 'eslint-plugin-react-hooks'; +import * as reactHooks from 'eslint-plugin-react-hooks'; export default [ { diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts index 42b55320fcd89..5ddbbcbb806fb 100644 --- a/packages/eslint-plugin-react-hooks/src/index.ts +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -4,8 +4,8 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import RulesOfHooks from './RulesOfHooks'; -import ExhaustiveDeps from './ExhaustiveDeps'; +import RulesOfHooks from './rules/RulesOfHooks'; +import ExhaustiveDeps from './rules/ExhaustiveDeps'; import type {ESLint, Linter, Rule} from 'eslint'; // All rules diff --git a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts b/packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts similarity index 100% rename from packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.ts rename to packages/eslint-plugin-react-hooks/src/rules/ExhaustiveDeps.ts diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts similarity index 100% rename from packages/eslint-plugin-react-hooks/src/RulesOfHooks.ts rename to packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts From ca12911d1fbe755b9b2b7d1bf548589317311a82 Mon Sep 17 00:00:00 2001 From: michael faith Date: Fri, 28 Feb 2025 10:22:08 -0600 Subject: [PATCH 72/73] feat(eslint-plugin-react-hooks): make flat config the `recommended` config (#32457) This change swaps which config `recommended` is aliasing. In 5.2.0, the new flat config was introduced as `recommended-latest`, while `recommended` still pointed at the legacy rc-based config, with a note that in the next major version `recommended` would be updated to point at `recommend-latest`. This change makes that swap, and make the default `recommended` experience the flat config. To continue using the legacy rc recommended config, please make the following change in your config ```diff - extends: ['plugin:react-hooks/recommended'] + extends: ['plugin:react-hooks/recommended-legacy'] ``` This change also deprecates `recommended-latest` in favor of `recommended`. `recommended-latest` will be removed in a future major version. The README has been updated to reflect the new usage, and to put the flat config sections before the legacy config sections. I also took the opportunity to change the v9 fixture to use a typescript config, serving as a demonstration for usage as well as a way to validate the types are correct. BREAKING CHANGE --------- Co-authored-by: lauren --- .../{eslint.config.mjs => eslint.config.ts} | 5 +- fixtures/eslint-v9/package.json | 5 +- fixtures/eslint-v9/tsconfig.json | 2 + fixtures/eslint-v9/yarn.lock | 76 +++++++++---------- packages/eslint-plugin-react-hooks/README.md | 59 +++++++------- .../eslint-plugin-react-hooks/src/index.ts | 35 +++++---- 6 files changed, 91 insertions(+), 91 deletions(-) rename fixtures/eslint-v9/{eslint.config.mjs => eslint.config.ts} (76%) create mode 100644 fixtures/eslint-v9/tsconfig.json diff --git a/fixtures/eslint-v9/eslint.config.mjs b/fixtures/eslint-v9/eslint.config.ts similarity index 76% rename from fixtures/eslint-v9/eslint.config.mjs rename to fixtures/eslint-v9/eslint.config.ts index ffc8375265c2b..62ef68671639e 100644 --- a/fixtures/eslint-v9/eslint.config.mjs +++ b/fixtures/eslint-v9/eslint.config.ts @@ -1,3 +1,4 @@ +import type {Linter} from 'eslint'; import * as reactHooks from 'eslint-plugin-react-hooks'; export default [ @@ -12,10 +13,10 @@ export default [ }, }, }, - reactHooks.configs['recommended-latest'], + reactHooks.configs['recommended'], { rules: { 'react-hooks/exhaustive-deps': 'error', }, }, -]; +] satisfies Linter.Config[]; diff --git a/fixtures/eslint-v9/package.json b/fixtures/eslint-v9/package.json index 58d0a94c0c58c..80827a0d1730a 100644 --- a/fixtures/eslint-v9/package.json +++ b/fixtures/eslint-v9/package.json @@ -2,8 +2,9 @@ "private": true, "name": "eslint-v9", "dependencies": { - "eslint": "^9", - "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks" + "eslint": "^9.18.0", + "eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks", + "jiti": "^2.4.2" }, "scripts": { "build": "node build.mjs && yarn", diff --git a/fixtures/eslint-v9/tsconfig.json b/fixtures/eslint-v9/tsconfig.json new file mode 100644 index 0000000000000..2c63c0851048d --- /dev/null +++ b/fixtures/eslint-v9/tsconfig.json @@ -0,0 +1,2 @@ +{ +} diff --git a/fixtures/eslint-v9/yarn.lock b/fixtures/eslint-v9/yarn.lock index 1583b73f14e21..a473b3a4ce165 100644 --- a/fixtures/eslint-v9/yarn.lock +++ b/fixtures/eslint-v9/yarn.lock @@ -14,7 +14,7 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.0": +"@eslint/config-array@^0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== @@ -23,24 +23,17 @@ debug "^4.3.1" minimatch "^3.1.2" -"@eslint/core@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091" - integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw== +"@eslint/core@^0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e" + integrity sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg== dependencies: "@types/json-schema" "^7.0.15" -"@eslint/core@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" - integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== - dependencies: - "@types/json-schema" "^7.0.15" - -"@eslint/eslintrc@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" - integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== +"@eslint/eslintrc@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz#96a558f45842989cca7ea1ecd785ad5491193846" + integrity sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -52,22 +45,22 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.20.0": - version "9.20.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" - integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== +"@eslint/js@9.21.0": + version "9.21.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.21.0.tgz#4303ef4e07226d87c395b8fad5278763e9c15c08" + integrity sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw== "@eslint/object-schema@^2.1.6": version "2.1.6" resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== -"@eslint/plugin-kit@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81" - integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A== +"@eslint/plugin-kit@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27" + integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g== dependencies: - "@eslint/core" "^0.10.0" + "@eslint/core" "^0.12.0" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -93,10 +86,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== -"@humanwhocodes/retry@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" - integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== +"@humanwhocodes/retry@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" + integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== "@types/estree@^1.0.6": version "1.0.6" @@ -231,21 +224,21 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9: - version "9.20.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" - integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== +eslint@^9.18.0: + version "9.21.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.21.0.tgz#b1c9c16f5153ff219791f627b94ab8f11f811591" + integrity sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.0" - "@eslint/core" "^0.11.0" - "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.20.0" - "@eslint/plugin-kit" "^0.2.5" + "@eslint/config-array" "^0.19.2" + "@eslint/core" "^0.12.0" + "@eslint/eslintrc" "^3.3.0" + "@eslint/js" "9.21.0" + "@eslint/plugin-kit" "^0.2.7" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.1" + "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" "@types/json-schema" "^7.0.15" ajv "^6.12.4" @@ -399,6 +392,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jiti@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" + integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== + js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index cf54caec986f5..e4223c18b20e0 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -18,9 +18,22 @@ npm install eslint-plugin-react-hooks --save-dev yarn add eslint-plugin-react-hooks --dev ``` +### Flat Config (eslint.config.js|ts) + +For [ESLint 9.0.0 and above](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/), add the `recommended` config. + +```js +import * as reactHooks from 'eslint-plugin-react-hooks'; + +export default [ + // ... + reactHooks.configs['recommended'], +]; +``` + ### Legacy Config (.eslintrc) -If you are still using ESLint below 9.0.0, please continue to use `recommended-legacy`. To avoid breaking changes, we still support `recommended` as well, but note that this will be changed to alias the flat recommended config in v6. +If you are still using ESLint below 9.0.0, you can use `recommended-legacy` for accessing the recommended config. ```js { @@ -31,25 +44,29 @@ If you are still using ESLint below 9.0.0, please continue to use `recommended-l } ``` -### Flat Config (eslint.config.js) +### Custom Configuration -For [ESLint 9.0.0 and above](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/) users, add the `recommended-latest` config. +If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file: + +#### Flat Config (eslint.config.js|ts) ```js import * as reactHooks from 'eslint-plugin-react-hooks'; export default [ - // ... - reactHooks.configs['recommended-latest'], + { + files: ['**/*.{js,jsx}'], + plugins: { 'react-hooks': reactHooks }, + // ... + rules: { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + } + }, ]; ``` -### Custom Configuration - -If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file: - #### Legacy Config (.eslintrc) - ```js { "plugins": [ @@ -64,24 +81,6 @@ If you want more fine-grained configuration, you can instead add a snippet like } ``` -#### Flat Config (eslint.config.js) - -```js -import * as reactHooks from 'eslint-plugin-react-hooks'; - -export default [ - { - files: ['**/*.{js,jsx}'], - plugins: { 'react-hooks': reactHooks }, - // ... - rules: { - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - } - }, -]; -``` - ## Advanced Configuration `exhaustive-deps` can be configured to validate dependencies of custom Hooks with the `additionalHooks` option. @@ -89,10 +88,10 @@ This option accepts a regex to match the names of custom Hooks that have depende ```js { - "rules": { + rules: { // ... "react-hooks/exhaustive-deps": ["warn", { - "additionalHooks": "(useMyCustomHook|useMyOtherCustomHook)" + additionalHooks: "(useMyCustomHook|useMyOtherCustomHook)" }] } } diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts index 5ddbbcbb806fb..61ac62912ddc2 100644 --- a/packages/eslint-plugin-react-hooks/src/index.ts +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -20,11 +20,16 @@ const configRules = { 'react-hooks/exhaustive-deps': 'warn', } satisfies Linter.RulesRecord; -// Legacy config -const legacyRecommendedConfig = { - plugins: ['react-hooks'], +// Flat config +const recommendedConfig = { + name: 'react-hooks/recommended', + plugins: { + get 'react-hooks'(): ESLint.Plugin { + return plugin; + }, + }, rules: configRules, -} satisfies Linter.LegacyConfig; +}; // Plugin object const plugin = { @@ -34,24 +39,18 @@ const plugin = { rules, configs: { /** Legacy recommended config, to be used with rc-based configurations */ - 'recommended-legacy': legacyRecommendedConfig, + 'recommended-legacy': { + plugins: ['react-hooks'], + rules: configRules, + }, /** - * 'recommended' is currently aliased to the legacy / rc recommended config) to maintain backwards compatibility. - * This is deprecated and in v6, it will switch to alias the flat recommended config. + * Recommended config, to be used with flat configs. */ - recommended: legacyRecommendedConfig, + recommended: recommendedConfig, - /** Latest recommended config, to be used with flat configurations */ - 'recommended-latest': { - name: 'react-hooks/recommended', - plugins: { - get 'react-hooks'(): ESLint.Plugin { - return plugin; - }, - }, - rules: configRules, - }, + /** @deprecated please use `recommended`; will be removed in v7 */ + 'recommended-latest': recommendedConfig, }, } satisfies ESLint.Plugin; From eda36a1c75ff8ac09fb127f6e04d4af16e49f50f Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 28 Feb 2025 13:06:40 -0500 Subject: [PATCH 73/73] [ci] Don't erroneously mark failures as successes (#32493) Randomly noticed this when I looked at a recent [DevTools regression test run](https://github.com/facebook/react/actions/runs/13578385011). I don't recall why we added `continue-on-error` previously, but I believe it was to keep all jobs in the matrix running even if one were to fail, in order to fully identify any failures from code changes like build or test failures. There is now a `fail-fast` option which does this. [`continue-on-error`](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error) now means: > Prevents a workflow run from failing when a job fails. Set to true to allow a workflow run to pass when this job fails. so it's not correct to use it. --- .github/workflows/compiler_typescript.yml | 2 +- .github/workflows/devtools_regression_tests.yml | 4 ++-- .github/workflows/runtime_build_and_test.yml | 8 +++++--- .github/workflows/runtime_eslint_plugin_e2e.yml | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/compiler_typescript.yml b/.github/workflows/compiler_typescript.yml index 6d9fa973350af..65fb789eccf17 100644 --- a/.github/workflows/compiler_typescript.yml +++ b/.github/workflows/compiler_typescript.yml @@ -75,8 +75,8 @@ jobs: name: Test ${{ matrix.workspace_name }} needs: discover_yarn_workspaces runs-on: ubuntu-latest - continue-on-error: true strategy: + fail-fast: false matrix: workspace_name: ${{ fromJSON(needs.discover_yarn_workspaces.outputs.matrix) }} steps: diff --git a/.github/workflows/devtools_regression_tests.yml b/.github/workflows/devtools_regression_tests.yml index 64d6707aa3688..4babfeefb0f2c 100644 --- a/.github/workflows/devtools_regression_tests.yml +++ b/.github/workflows/devtools_regression_tests.yml @@ -100,6 +100,7 @@ jobs: needs: build_devtools_and_process_artifacts runs-on: ubuntu-latest strategy: + fail-fast: false matrix: version: - "16.0" @@ -108,7 +109,6 @@ jobs: - "17.0" - "18.0" - "18.2" # compiler polyfill - continue-on-error: true steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -135,6 +135,7 @@ jobs: needs: build_devtools_and_process_artifacts runs-on: ubuntu-latest strategy: + fail-fast: false matrix: version: - "16.0" @@ -142,7 +143,6 @@ jobs: - "16.8" # hooks - "17.0" - "18.0" - continue-on-error: true steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 diff --git a/.github/workflows/runtime_build_and_test.yml b/.github/workflows/runtime_build_and_test.yml index 12e341a86fd96..d53e352fbb266 100644 --- a/.github/workflows/runtime_build_and_test.yml +++ b/.github/workflows/runtime_build_and_test.yml @@ -38,8 +38,8 @@ jobs: name: Flow check ${{ matrix.flow_inline_config_shortname }} needs: discover_flow_inline_configs runs-on: ubuntu-latest - continue-on-error: true strategy: + fail-fast: false matrix: flow_inline_config_shortname: ${{ fromJSON(needs.discover_flow_inline_configs.outputs.matrix) }} steps: @@ -117,6 +117,7 @@ jobs: name: yarn test ${{ matrix.params }} (Shard ${{ matrix.shard }}) runs-on: ubuntu-latest strategy: + fail-fast: false matrix: params: - "-r=stable --env=development" @@ -144,7 +145,6 @@ jobs: - 3/5 - 4/5 - 5/5 - continue-on-error: true steps: - uses: actions/checkout@v4 with: @@ -170,6 +170,7 @@ jobs: name: yarn build and lint runs-on: ubuntu-latest strategy: + fail-fast: false matrix: # yml is dumb. update the --total arg to yarn build if you change the number of workers worker_id: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19] @@ -215,6 +216,7 @@ jobs: name: yarn test-build needs: build_and_lint strategy: + fail-fast: false matrix: test_params: [ # Intentionally passing these as strings instead of creating a @@ -250,7 +252,6 @@ jobs: - 1/3 - 2/3 - 3/3 - continue-on-error: true runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -500,6 +501,7 @@ jobs: needs: build_and_lint runs-on: ubuntu-latest strategy: + fail-fast: false matrix: browser: [chrome, firefox, edge] steps: diff --git a/.github/workflows/runtime_eslint_plugin_e2e.yml b/.github/workflows/runtime_eslint_plugin_e2e.yml index edc188f38622e..f8878548c0597 100644 --- a/.github/workflows/runtime_eslint_plugin_e2e.yml +++ b/.github/workflows/runtime_eslint_plugin_e2e.yml @@ -20,13 +20,13 @@ jobs: name: ESLint v${{ matrix.eslint_major }} runs-on: ubuntu-latest strategy: + fail-fast: false matrix: eslint_major: - "6" - "7" - "8" - "9" - continue-on-error: true steps: - uses: actions/checkout@v4 with: