From 4049cfeeab33146e02b0721477fd5f2020f76a04 Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Fri, 22 Aug 2025 16:58:01 -0400 Subject: [PATCH] Update Flow to 0.273 (#34274) This version introduces "Natural Inference" which requires a couple more type annotations to make Flow pass. --- .eslintrc.js | 1 + package.json | 4 ++-- .../src/backend/fiber/renderer.js | 2 +- .../react-devtools-shared/src/hookNamesCache.js | 2 +- .../hooks/parseHookNames/loadSourceAndMetadata.js | 2 +- .../src/import-worker/preprocessData.js | 6 +++--- .../src/client/inputValueTracking.js | 9 ++++++--- .../src/events/SyntheticEvent.js | 14 +++++++------- .../src/shared/ReactDOMFormActions.js | 2 +- .../legacy-events/ResponderTouchHistoryStore.js | 1 + .../react-reconciler/src/ReactFiberCommitWork.js | 4 ++-- .../react-test-renderer/src/ReactTestRenderer.js | 3 ++- packages/scheduler/src/forks/SchedulerPostTask.js | 2 +- .../src/useSyncExternalStoreWithSelector.js | 12 ++++++------ yarn.lock | 10 +++++----- 15 files changed, 40 insertions(+), 34 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 85783f6c5a..e8ace6311d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -565,6 +565,7 @@ module.exports = { BigInt: 'readonly', BigInt64Array: 'readonly', BigUint64Array: 'readonly', + CacheType: 'readonly', Class: 'readonly', ClientRect: 'readonly', CopyInspectedElementPath: 'readonly', diff --git a/package.json b/package.json index e7aa5bf8a5..3c732e60f3 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,8 @@ "eslint-plugin-react-internal": "link:./scripts/eslint-rules", "fbjs-scripts": "^3.0.1", "filesize": "^6.0.1", - "flow-bin": "^0.272", - "flow-remove-types": "^2.272", + "flow-bin": "^0.273", + "flow-remove-types": "^2.273", "flow-typed": "^4.1.1", "glob": "^7.1.6", "glob-stream": "^6.1.0", diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index cc41ef4ea7..88a9b70093 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -6705,7 +6705,7 @@ export function attach( if (isMostRecentlyInspectedElement(id) && !forceFullData) { if (!hasElementUpdatedSinceLastInspected) { if (path !== null) { - let secondaryCategory = null; + let secondaryCategory: 'suspendedBy' | 'hooks' | null = null; if (path[0] === 'hooks') { secondaryCategory = 'hooks'; } diff --git a/packages/react-devtools-shared/src/hookNamesCache.js b/packages/react-devtools-shared/src/hookNamesCache.js index f85bb3c6d8..669f5f31c2 100644 --- a/packages/react-devtools-shared/src/hookNamesCache.js +++ b/packages/react-devtools-shared/src/hookNamesCache.js @@ -103,7 +103,7 @@ export function loadHookNames( let timeoutID: $FlowFixMe | null; let didTimeout = false; - let status = 'unknown'; + let status: 'success' | 'error' | 'timeout' | 'unknown' = 'unknown'; let resolvedHookNames: HookNames | null = null; const wake = () => { diff --git a/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js index 2a72da0ce2..5001ff31bf 100644 --- a/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js +++ b/packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js @@ -64,7 +64,7 @@ import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/view // Prefer a cached albeit stale response to reduce download time. // We wouldn't want to load/parse a newer version of the source (even if one existed). -const FETCH_OPTIONS = {cache: 'force-cache'}; +const FETCH_OPTIONS = {cache: 'force-cache' as CacheType}; const MAX_SOURCE_LENGTH = 100_000_000; diff --git a/packages/react-devtools-timeline/src/import-worker/preprocessData.js b/packages/react-devtools-timeline/src/import-worker/preprocessData.js index cefcd1e7f4..f3186e65a7 100644 --- a/packages/react-devtools-timeline/src/import-worker/preprocessData.js +++ b/packages/react-devtools-timeline/src/import-worker/preprocessData.js @@ -509,7 +509,7 @@ function processTimelineEvent( } else if (name.startsWith('--schedule-forced-update-')) { const [laneBitmaskString, componentName] = name.slice(25).split('-'); - const forceUpdateEvent = { + const forceUpdateEvent: SchedulingEvent = { type: 'schedule-force-update', lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString), componentName, @@ -527,7 +527,7 @@ function processTimelineEvent( } else if (name.startsWith('--schedule-state-update-')) { const [laneBitmaskString, componentName] = name.slice(24).split('-'); - const stateUpdateEvent = { + const stateUpdateEvent: SchedulingEvent = { type: 'schedule-state-update', lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString), componentName, @@ -578,7 +578,7 @@ function processTimelineEvent( // We can't know if they'll be resolved or not at this point. // We'll just give them a default (fake) duration width. - const suspenseEvent = { + const suspenseEvent: SuspenseEvent = { componentName, depth, duration: null, diff --git a/packages/react-dom-bindings/src/client/inputValueTracking.js b/packages/react-dom-bindings/src/client/inputValueTracking.js index f89617fe6d..e931283e60 100644 --- a/packages/react-dom-bindings/src/client/inputValueTracking.js +++ b/packages/react-dom-bindings/src/client/inputValueTracking.js @@ -140,7 +140,7 @@ export function trackHydrated( return false; } - let valueField; + let valueField: 'checked' | 'value'; let expectedValue; if (isCheckable(node)) { valueField = 'checked'; @@ -150,8 +150,11 @@ export function trackHydrated( valueField = 'value'; expectedValue = initialValue; } - // eslint-disable-next-line react-internal/safe-string-coercion - const currentValue = '' + (node[valueField]: any); + const currentValue = + // eslint-disable-next-line react-internal/safe-string-coercion + '' + + (// $FlowFixMe[prop-missing] + node[valueField]: any); node._valueTracker = trackValueOnNode(node, valueField, expectedValue); return currentValue !== expectedValue; } diff --git a/packages/react-dom-bindings/src/events/SyntheticEvent.js b/packages/react-dom-bindings/src/events/SyntheticEvent.js index 469cd0f010..acd50cf8e0 100644 --- a/packages/react-dom-bindings/src/events/SyntheticEvent.js +++ b/packages/react-dom-bindings/src/events/SyntheticEvent.js @@ -146,7 +146,7 @@ function createSyntheticEvent(Interface: EventInterfaceType) { * @interface Event * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ -const EventInterface = { +const EventInterface: EventInterfaceType = { eventPhase: 0, bubbles: 0, cancelable: 0, @@ -442,7 +442,7 @@ function getEventModifierState(nativeEvent: {[propName: string]: mixed}) { * @interface KeyboardEvent * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ -const KeyboardEventInterface = { +const KeyboardEventInterface: EventInterfaceType = { ...UIEventInterface, key: getEventKey, code: 0, @@ -505,7 +505,7 @@ export const SyntheticKeyboardEvent: $FlowFixMe = createSyntheticEvent( * @interface PointerEvent * @see http://www.w3.org/TR/pointerevents/ */ -const PointerEventInterface = { +const PointerEventInterface: EventInterfaceType = { ...MouseEventInterface, pointerId: 0, width: 0, @@ -526,7 +526,7 @@ export const SyntheticPointerEvent: $FlowFixMe = createSyntheticEvent( * @interface TouchEvent * @see http://www.w3.org/TR/touch-events/ */ -const TouchEventInterface = { +const TouchEventInterface: EventInterfaceType = { ...UIEventInterface, touches: 0, targetTouches: 0, @@ -545,7 +545,7 @@ export const SyntheticTouchEvent: $FlowFixMe = * @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events- * @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent */ -const TransitionEventInterface = { +const TransitionEventInterface: EventInterfaceType = { ...EventInterface, propertyName: 0, elapsedTime: 0, @@ -559,7 +559,7 @@ export const SyntheticTransitionEvent: $FlowFixMe = createSyntheticEvent( * @interface WheelEvent * @see http://www.w3.org/TR/DOM-Level-3-Events/ */ -const WheelEventInterface = { +const WheelEventInterface: EventInterfaceType = { ...MouseEventInterface, deltaX(event: {[propName: string]: mixed}) { return 'deltaX' in event @@ -594,7 +594,7 @@ const WheelEventInterface = { export const SyntheticWheelEvent: $FlowFixMe = createSyntheticEvent(WheelEventInterface); -const ToggleEventInterface = { +const ToggleEventInterface: EventInterfaceType = { ...EventInterface, newState: 0, oldState: 0, diff --git a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js index 6dd4e4da44..6978f48845 100644 --- a/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js +++ b/packages/react-dom-bindings/src/shared/ReactDOMFormActions.js @@ -31,7 +31,7 @@ export type FormStatus = FormStatusPending | FormStatusNotPending; // Since the "not pending" value is always the same, we can reuse the // same object across all transitions. -const sharedNotPendingObject = { +const sharedNotPendingObject: FormStatusNotPending = { pending: false, data: null, method: null, diff --git a/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js b/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js index 50264d6c71..4b523b0a92 100644 --- a/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js +++ b/packages/react-native-renderer/src/legacy-events/ResponderTouchHistoryStore.js @@ -207,6 +207,7 @@ const ResponderTouchHistoryStore = { touchHistory.numberActiveTouches = nativeEvent.touches.length; if (touchHistory.numberActiveTouches === 1) { touchHistory.indexOfSingleActiveTouch = + // $FlowFixMe[incompatible-type] might be null according to type nativeEvent.touches[0].identifier; } } else if (isEndish(topLevelType)) { diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 8689f2eacf..810ddf5fcb 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -5020,7 +5020,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber( const instance: OffscreenInstance = offscreenFiber.stateNode; const transitions = instance._transitions; if (transitions !== null) { - const abortReason = { + const abortReason: TransitionAbort = { reason: 'suspense', name: current.memoizedProps.name || null, }; @@ -5061,7 +5061,7 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber( const instance: TracingMarkerInstance = current.stateNode; const transitions = instance.transitions; if (transitions !== null) { - const abortReason = { + const abortReason: TransitionAbort = { reason: 'marker', name: current.memoizedProps.name, }; diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index d4466b3427..08676503fa 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -9,6 +9,7 @@ import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; import type { + Container, PublicInstance, Instance, TextInstance, @@ -505,7 +506,7 @@ function create( isStrictMode = true; } } - let container = { + let container: Container = { children: ([]: Array), createNodeMock, tag: 'CONTAINER', diff --git a/packages/scheduler/src/forks/SchedulerPostTask.js b/packages/scheduler/src/forks/SchedulerPostTask.js index ab762bbd43..700ff2e5d2 100644 --- a/packages/scheduler/src/forks/SchedulerPostTask.js +++ b/packages/scheduler/src/forks/SchedulerPostTask.js @@ -76,7 +76,7 @@ export function unstable_scheduleCallback( callback: SchedulerCallback, options?: {delay?: number}, ): CallbackNode { - let postTaskPriority; + let postTaskPriority: PostTaskPriorityLevel; switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js index a7be5c0fe8..ddbd77c698 100644 --- a/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js +++ b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js @@ -23,8 +23,7 @@ export function useSyncExternalStoreWithSelector( selector: (snapshot: Snapshot) => Selection, isEqual?: (a: Selection, b: Selection) => boolean, ): Selection { - // Use this to track the rendered snapshot. - const instRef = useRef< + type Inst = | { hasValue: true, value: Selection, @@ -32,10 +31,11 @@ export function useSyncExternalStoreWithSelector( | { hasValue: false, value: null, - } - | null, - >(null); - let inst; + }; + + // Use this to track the rendered snapshot. + const instRef = useRef(null); + let inst: Inst; if (instRef.current === null) { inst = { hasValue: false, diff --git a/yarn.lock b/yarn.lock index 5574fb11a4..6f9e400d9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9298,12 +9298,12 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -flow-bin@^0.272: - version "0.272.2" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.272.2.tgz#918fc58dc7a99725a83314f453d3e49a56aebdc5" - integrity sha512-Rf8UG1biRBUGGh6qN7Ua2Y2lJRpR8Pbzby5kfHQ5m5SgjC5eOPw3Qjbrheb9ec5oU4L1gCOXRYbkbpr02PRUBw== +flow-bin@^0.273: + version "0.273.1" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.273.1.tgz#18621f169ecbabe3656c56980ebaabc439ee76c1" + integrity sha512-OlJkNCSd+i6z5xDoyGiS+3X5xYQx+vVUY2iUw6cHJR0LK3ttyA1wkiI93OuBALhLhF91KayzGhRSzXHqA75iUw== -flow-remove-types@^2.272: +flow-remove-types@^2.273: version "2.279.0" resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.279.0.tgz#3a3388d9158eba0f82c40d80d31d9640b883a3f5" integrity sha512-bPFloMR/A2b/r/sIsf7Ix0LaMicCJNjwhXc4xEEQVzJCIz5u7C7XDaEOXOiqveKlCYK7DcBNn6R01Cbbc9gsYA==