Fix TS exactOptionalPropertyTypes compatibility for generated types (#57628) (#57699)

Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57628

NOTE: Patches over a `flow-api-translator` bug, which I'll fix upstream later. We need to pick this to `0.87-stable` to resolve user integration issues.

**Context**

TypeScript's `exactOptionalPropertyTypes` flag (strict mode) creates a distinction between `foo?: T` and `foo?: T | undefined`.

```js
// Flow's semantics

interface Props {
  onRefresh?: () => void;
}
const a: Props = { onRefresh: undefined }; // ✅ ok
```
```ts
// TypeScript with exactOptionalPropertyTypes: true (i.e. strict mode)

interface Props {
  onRefresh?: () => void;
}
const a: Props = { onRefresh: undefined }; // ❌ error

interface PropsFixed {
  onRefresh?: (() => void) | undefined;
}
const b: PropsFixed = { onRefresh: undefined }; // ✅ ok
```

With this added strictness in TypeScript, our generated types via `flow-api-translator` could create downstream type incompatibility in apps.

**This diff**

Patches the above issue in React Native's Flow → TS `types_generated/` pipeline. We transform all instances to the wider `foo?: T | undefined` format, for maximum compatibility.

**Notes**

`foo?: T [| undefined]` **remains stripped** in the API snapshot (existing transform with the aim of a concise format). There is a net, nonfunctional snapshot diff around function members, which (as a positive result) are re-ordered.

Changelog:
[General][Fixed] - **Strict TypeScript API**: Optional property types are now widened to explicitly include `| undefined` for `exactOptionalPropertyTypes` compatibility

Reviewed By: cipolleschi

Differential Revision: D113030161

fbshipit-source-id: 3ab005edab6b80b18fbb9ae7125ba56e3bd94195

Co-authored-by: Alex Hunt <huntie@meta.com>
This commit is contained in:
Zeya Peng
2026-07-27 09:35:50 -04:00
committed by GitHub
co-authored by Alex Hunt
parent 330080c46b
commit 9a7b821a02
6 changed files with 317 additions and 148 deletions
+145 -145
View File
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<883e9c745f0864d256fd0a4760f97b1c>>
* @generated SignedSource<<955e3ecbb2aa295ace2e98ea3a2d3018>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
@@ -1711,10 +1711,10 @@ declare interface ButtonProps {
readonly nextFocusRight?: number
readonly nextFocusUp?: number
readonly onAccessibilityAction?: (event: AccessibilityActionEvent) => unknown
readonly onPress?: (event?: GestureResponderEvent) => unknown
readonly testID?: string
readonly title: string
readonly touchSoundDisabled?: boolean
readonly onPress?: (event?: GestureResponderEvent) => unknown
}
declare function cancelHeadlessTask(taskId: number, taskKey: string): void
declare type Category = string
@@ -1741,9 +1741,9 @@ declare type CellRendererProps<ItemT> = {
readonly children: React.ReactNode
readonly index: number
readonly item: ItemT
readonly style: StyleProp<ViewStyle>
readonly onFocusCapture?: (event: FocusEvent) => void
readonly onLayout?: (event: LayoutChangeEvent) => void
readonly style: StyleProp<ViewStyle>
}
declare class CellRenderMask {
addCells(cells: { first: number; last: number }): void
@@ -2683,12 +2683,12 @@ declare type InterpolationConfigType<
OutputT extends InterpolationConfigSupportedOutputType,
> = Readonly<
AnimatedNodeConfig & {
easing?: (input: number) => number
extrapolate?: ExtrapolateType
extrapolateLeft?: ExtrapolateType
extrapolateRight?: ExtrapolateType
inputRange: ReadonlyArray<number>
outputRange: ReadonlyArray<OutputT>
easing?: (input: number) => number
}
>
declare type IOSKeyboardEvent = Readonly<
@@ -3350,6 +3350,14 @@ declare type OptionalFlatListProps<ItemT> = {
end: number
start: number
}
getItemLayout?: (
data: Readonly<ArrayLike<ItemT>> | undefined,
index: number,
) => {
index: number
length: number
offset: number
}
horizontal?: boolean
initialNumToRender?: number
initialScrollIndex?: number
@@ -3359,14 +3367,6 @@ declare type OptionalFlatListProps<ItemT> = {
removeClippedSubviews?: boolean
renderItem?: ListRenderItem<ItemT>
strictMode?: boolean
getItemLayout?: (
data: Readonly<ArrayLike<ItemT>> | undefined,
index: number,
) => {
index: number
length: number
offset: number
}
}
declare type OptionalPlatformSelectSpec<T> = { [key in PlatformOSType]?: T }
declare type OptionalSectionListProps<ItemT, SectionT = DefaultSectionT> = {
@@ -3383,6 +3383,14 @@ declare type OptionalVirtualizedListProps = {
debug?: boolean
disableVirtualization?: boolean
extraData?: any
getItemLayout?: (
data: any,
index: number,
) => {
index: number
length: number
offset: number
}
horizontal?: boolean
initialNumToRender?: number
initialScrollIndex?: number
@@ -3416,29 +3424,17 @@ declare type OptionalVirtualizedListProps = {
refreshing?: boolean
removeClippedSubviews?: boolean
renderItem?: ListRenderItem<Item>
renderScrollComponent?: (props: ScrollViewProps) => React.JSX.Element
updateCellsBatchingPeriod?: number
viewabilityConfig?: ViewabilityConfig
viewabilityConfigCallbackPairs?: Array<ViewabilityConfigCallbackPair>
windowSize?: number
getItemLayout?: (
data: any,
index: number,
) => {
index: number
length: number
offset: number
}
renderScrollComponent?: (props: ScrollViewProps) => React.JSX.Element
}
declare type OptionalVirtualizedSectionListProps<
ItemT,
SectionT = DefaultVirtualizedSectionT,
> = {
onEndReached?: ($$PARAM_0$$: { distanceFromEnd: number }) => void
renderSectionFooter?: (info: { section: SectionT }) => null | React.ReactNode
renderSectionHeader?: (info: { section: SectionT }) => null | React.ReactNode
SectionSeparatorComponent?: React.ComponentType<any>
stickySectionHeadersEnabled?: boolean
renderItem?: (info: {
index: number
item: ItemT
@@ -3449,6 +3445,10 @@ declare type OptionalVirtualizedSectionListProps<
updateProps: (select: "leading" | "trailing", newProps: Object) => void
}
}) => null | React.ReactNode
renderSectionFooter?: (info: { section: SectionT }) => null | React.ReactNode
renderSectionHeader?: (info: { section: SectionT }) => null | React.ReactNode
SectionSeparatorComponent?: React.ComponentType<any>
stickySectionHeadersEnabled?: boolean
}
declare type OrientationChangeEvent = {
readonly orientation: "landscape" | "portrait"
@@ -3687,13 +3687,13 @@ declare type PressabilityConfig = {
readonly pressRectOffset?: RectOrSize
}
declare type PressabilityEventHandlers = {
readonly onBlur: (event: BlurEvent) => void
readonly onClick: (event: GestureResponderEvent) => void
readonly onFocus: (event: FocusEvent) => void
readonly onMouseEnter?: (event: MouseEvent) => void
readonly onMouseLeave?: (event: MouseEvent) => void
readonly onPointerEnter?: (event: PointerEvent) => void
readonly onPointerLeave?: (event: PointerEvent) => void
readonly onBlur: (event: BlurEvent) => void
readonly onClick: (event: GestureResponderEvent) => void
readonly onFocus: (event: FocusEvent) => void
readonly onResponderGrant: (event: GestureResponderEvent) => boolean | void
readonly onResponderMove: (event: GestureResponderEvent) => void
readonly onResponderRelease: (event: GestureResponderEvent) => void
@@ -4353,6 +4353,14 @@ declare type ScrollViewBaseProps = {
readonly autoscrollToTopThreshold?: number
readonly minIndexForVisible: number
}
readonly onContentSizeChange?: (
contentWidth: number,
contentHeight: number,
) => void
readonly onKeyboardDidHide?: (event: KeyboardEvent) => void
readonly onKeyboardDidShow?: (event: KeyboardEvent) => void
readonly onKeyboardWillHide?: (event: KeyboardEvent) => void
readonly onKeyboardWillShow?: (event: KeyboardEvent) => void
readonly onMomentumScrollBegin?: (event: ScrollEvent) => void
readonly onMomentumScrollEnd?: (event: ScrollEvent) => void
readonly onScroll?: (event: ScrollEvent) => void
@@ -4373,14 +4381,6 @@ declare type ScrollViewBaseProps = {
readonly StickyHeaderComponent?: StickyHeaderComponentType
readonly stickyHeaderHiddenOnScroll?: boolean
readonly stickyHeaderIndices?: ReadonlyArray<number>
readonly onContentSizeChange?: (
contentWidth: number,
contentHeight: number,
) => void
readonly onKeyboardDidHide?: (event: KeyboardEvent) => void
readonly onKeyboardDidShow?: (event: KeyboardEvent) => void
readonly onKeyboardWillHide?: (event: KeyboardEvent) => void
readonly onKeyboardWillShow?: (event: KeyboardEvent) => void
}
declare type ScrollViewComponentStatics = {
readonly Context: typeof $$ScrollViewContext
@@ -4461,13 +4461,13 @@ declare type ScrollViewPropsIOS = {
readonly indicatorStyle?: "black" | "default" | "white"
readonly maximumZoomScale?: number
readonly minimumZoomScale?: number
readonly onScrollToTop?: (event: ScrollEvent) => void
readonly pinchGestureEnabled?: boolean
readonly scrollIndicatorInsets?: EdgeInsetsProp
readonly scrollsToTop?: boolean
readonly scrollToOverflowEnabled?: boolean
readonly showsHorizontalScrollIndicator?: boolean
readonly zoomScale?: number
readonly onScrollToTop?: (event: ScrollEvent) => void
}
declare interface ScrollViewScrollToOptions {
animated?: boolean
@@ -4491,6 +4491,10 @@ declare type SectionBase<
data: ReadonlyArray<SectionItemT>
ItemSeparatorComponent?: React.ComponentType<any> | React.JSX.Element
key?: string
keyExtractor?: (
item: SectionItemT | undefined,
index?: number | undefined,
) => string
renderItem?: (info: {
index: number
item: SectionItemT
@@ -4501,10 +4505,6 @@ declare type SectionBase<
updateProps: (select: "leading" | "trailing", newProps: Object) => void
}
}) => null | React.JSX.Element
keyExtractor?: (
item: SectionItemT | undefined,
index?: number | undefined,
) => string
}
declare class SectionList<
ItemT = any,
@@ -4634,6 +4634,17 @@ declare type ShareOptions = {
}
declare interface Spec extends TurboModule {
readonly blur?: (reactTag: number) => void
readonly focus?: (reactTag: number) => void
readonly getConstantsForViewManager?: (
viewManagerName: string,
) => Object | undefined
readonly getDefaultEventTypes?: () => Array<string>
readonly lazilyLoadView?: (name: string) => Object
readonly sendAccessibilityEvent?: (
reactTag: number,
eventType: number,
) => void
readonly setLayoutAnimationEnabledExperimental?: (enabled: boolean) => void
readonly clearJSResponder: () => void
readonly configureNextLayoutAnimation: (
config: Object,
@@ -4662,13 +4673,7 @@ declare interface Spec extends TurboModule {
height: number,
) => void,
) => void
readonly focus?: (reactTag: number) => void
readonly getConstants: () => Object
readonly getConstantsForViewManager?: (
viewManagerName: string,
) => Object | undefined
readonly getDefaultEventTypes?: () => Array<string>
readonly lazilyLoadView?: (name: string) => Object
readonly manageChildren: (
containerTag: number,
moveFromIndices: Array<number>,
@@ -4701,16 +4706,11 @@ declare interface Spec extends TurboModule {
height: number,
) => void,
) => void
readonly sendAccessibilityEvent?: (
reactTag: number,
eventType: number,
) => void
readonly setChildren: (containerTag: number, reactTags: Array<number>) => void
readonly setJSResponder: (
reactTag: number,
blockNativeResponder: boolean,
) => void
readonly setLayoutAnimationEnabledExperimental?: (enabled: boolean) => void
readonly updateView: (
reactTag: number,
viewName: string,
@@ -5318,6 +5318,7 @@ declare type TimingAnimationConfig = Readonly<
AnimationConfig & {
delay?: number
duration?: number
easing?: (value: number) => number
toValue:
| AnimatedColor_default
| AnimatedInterpolation_default<number>
@@ -5329,7 +5330,6 @@ declare type TimingAnimationConfig = Readonly<
readonly x: number
readonly y: number
}
easing?: (value: number) => number
}
>
declare type ToastAndroid = typeof ToastAndroid
@@ -5859,22 +5859,22 @@ export {
AccessibilityActionEvent, // a0d4daa0
AccessibilityActionInfo, // db47a917
AccessibilityInfo, // 23a3aa9b
AccessibilityProps, // 21f32d3a
AccessibilityProps, // eda80653
AccessibilityRole, // f2f2e066
AccessibilityState, // b0c2b3f7
AccessibilityValue, // cf8bcb74
ActionSheetIOS, // b558559e
ActionSheetIOSOptions, // 1756eb5a
ActivityIndicator, // 0f796809
ActivityIndicator, // e43c68bb
ActivityIndicatorInstance, // a82dd4e7
ActivityIndicatorProps, // a232e3ff
Alert, // 5bf12165
ActivityIndicatorProps, // 619842eb
Alert, // a398a509
AlertButton, // bf1a3b60
AlertButtonStyle, // ec9fb242
AlertOptions, // a0cdac0f
AlertOptions, // 8a116d2a
AlertType, // 5ab91217
AndroidKeyboardEvent, // e03becc8
Animated, // 91a7b171
Animated, // 50cbe576
AppConfig, // 35c0ca70
AppRegistry, // 1e8c5a00
AppState, // 12012be5
@@ -5888,9 +5888,9 @@ export {
BackPressEventName, // 4620fb76
BlurEvent, // 4ba4f941
BoxShadowValue, // b679703f
Button, // 2e36a068
ButtonInstance, // 0a11787b
ButtonProps, // 4a8a20e3
Button, // bc8f6464
ButtonInstance, // d94b4241
ButtonProps, // 21c5780c
Clipboard, // 41addb89
CodegenTypes, // ab4986cc
ColorSchemeName, // 6615edd6
@@ -5908,9 +5908,9 @@ export {
DimensionsPayload, // 653bc26c
DisplayMetrics, // 1dc35cef
DisplayMetricsAndroid, // 872e62eb
DrawerLayoutAndroid, // a8ced056
DrawerLayoutAndroid, // 7843b7b5
DrawerLayoutAndroidInstance, // c0694352
DrawerLayoutAndroidProps, // e9a67dd7
DrawerLayoutAndroidProps, // 0c1d6155
DrawerSlideEvent, // c4ab8fba
DropShadowValue, // e9df2606
DynamicColorIOS, // d96c228c
@@ -5926,39 +5926,39 @@ export {
EventSubscription, // b8d084aa
ExtendedExceptionData, // 5a6ccf5a
FilterFunction, // bf24c0e3
FlatList, // 0f2531c9
FlatListInstance, // f31d4744
FlatListProps, // b8b5adf2
FlatList, // e1b005c7
FlatListInstance, // 2d1d8e45
FlatListProps, // 94fa2dc7
FocusEvent, // 850f1517
FontVariant, // 7c7558bb
GestureResponderEvent, // 14d3e77a
GestureResponderHandlers, // c976c2ea
GestureResponderHandlers, // b540de80
HostComponent, // a611806a
HostInstance, // f78dcaf8
I18nManager, // f9870e00
IEventEmitter, // fbef6131
IOSKeyboardEvent, // e67bfe3a
IgnorePattern, // ec6f6ece
Image, // fba54a35
ImageBackground, // 1543bae2
ImageBackgroundInstance, // 610d9eed
ImageBackgroundProps, // ceb153c3
Image, // 87b793a4
ImageBackground, // abfd6c1d
ImageBackgroundInstance, // 50bc4cb3
ImageBackgroundProps, // e04f173a
ImageErrorEvent, // 978933f4
ImageInstance, // 9a100753
ImageLoadEvent, // 77f0b718
ImageProgressEventIOS, // 445331a4
ImageProps, // e8aecf89
ImagePropsAndroid, // 9fd9bcbb
ImagePropsBase, // f5535cef
ImagePropsIOS, // 4a080668
ImageProps, // c50cfe0d
ImagePropsAndroid, // ee00e1d5
ImagePropsBase, // fa3f8e21
ImagePropsIOS, // 9e19c85d
ImageRequireSource, // 681d683b
ImageResizeMode, // d51106e2
ImageResolvedAssetSource, // f3060931
ImageSize, // 1c47cf88
ImageSource, // 48c7f316
ImageSourcePropType, // bfb5e5c6
ImageSource, // ea31cf4a
ImageSourcePropType, // f522e093
ImageStyle, // 4b7c1906
ImageURISource, // 016eb083
ImageURISource, // 443d047c
InputAccessoryView, // 9695d393
InputAccessoryViewProps, // d94dc290
InputModeOptions, // 4e8581b9
@@ -5967,9 +5967,9 @@ export {
KeyEvent, // 20fa4267
KeyUpEvent, // 57f832c5
Keyboard, // 49414c97
KeyboardAvoidingView, // cf0307d2
KeyboardAvoidingViewInstance, // c4ca43a7
KeyboardAvoidingViewProps, // cf148633
KeyboardAvoidingView, // fbf69b90
KeyboardAvoidingViewInstance, // f6c63fc9
KeyboardAvoidingViewProps, // 8609524c
KeyboardEvent, // c3f895d4
KeyboardEventEasing, // af4091c8
KeyboardEventName, // 59299ad6
@@ -5994,12 +5994,12 @@ export {
MeasureInWindowOnSuccessCallback, // a285f598
MeasureLayoutOnSuccessCallback, // 3592502a
MeasureOnSuccessCallback, // 82824e59
Modal, // 43f50013
ModalBaseProps, // ac3931ad
Modal, // 252d448a
ModalBaseProps, // c294cc46
ModalInstance, // d466ce77
ModalProps, // 35252d68
ModalProps, // 757e7cec
ModalPropsAndroid, // 515fb173
ModalPropsIOS, // 0e13cfcc
ModalPropsIOS, // 664ecb7e
ModeChangeEvent, // f64bf69d
MouseEvent, // d1f889fd
NativeAppEventEmitter, // 08d4c47d
@@ -6017,10 +6017,10 @@ export {
Networking, // bbc5be42
OpaqueColorValue, // 25f3fa5b
PackagerAsset, // d1c88cf4
PanResponder, // f8f71cac
PanResponderCallbacks, // 6d63e7be
PanResponder, // e4df325a
PanResponderCallbacks, // c3dafe87
PanResponderGestureState, // 54baf558
PanResponderInstance, // 69cebbe8
PanResponderInstance, // 8ccc05bf
Permission, // 08f1c82f
PermissionStatus, // 4b7de97b
PermissionsAndroid, // 8a0bc8d8
@@ -6031,17 +6031,17 @@ export {
PlatformSelectSpec, // 09ed7758
PointValue, // 69db075f
PointerEvent, // ff599afe
PressabilityConfig, // faab5639
PressabilityEventHandlers, // 0b910091
Pressable, // f4911021
PressabilityConfig, // fea539a5
PressabilityEventHandlers, // c222648b
Pressable, // 7008c06f
PressableAndroidRippleConfig, // ee32eaca
PressableInstance, // eebfe911
PressableProps, // 95d57db2
PressableProps, // d6bfa55b
PressableStateCallbackType, // 9af36561
ProcessedColorValue, // 33f74304
ProgressBarAndroid, // 44a46d48
ProgressBarAndroid, // 7b9eaaf0
ProgressBarAndroidInstance, // ab545ef1
ProgressBarAndroidProps, // f20ebde5
ProgressBarAndroidProps, // 07fa83a1
PublicRootInstance, // 8040afd7
PublicTextInstance, // 6937c7bf
PushNotificationEventName, // 84e7e150
@@ -6049,10 +6049,10 @@ export {
PushNotificationPermissions, // c2e7ae4f
Rationale, // 5df1b1c1
ReactNativeVersion, // abd76827
RefreshControl, // 5b3465d2
RefreshControlInstance, // 8b4d078b
RefreshControlProps, // f8883f9a
RefreshControlPropsAndroid, // 99f64c97
RefreshControl, // 947cb880
RefreshControlInstance, // 55f1814a
RefreshControlProps, // eb2b1cbe
RefreshControlPropsAndroid, // 8ac931ca
RefreshControlPropsIOS, // 72a36381
Registry, // 6c39216d
ResponderSyntheticEvent, // ebfa7f48
@@ -6063,26 +6063,26 @@ export {
RootViewStyleProvider, // 8792d506
Runnable, // 594dd93a
Runnables, // 4367c557
SafeAreaView, // 47b1ede1
SafeAreaView, // 9ce03f75
SafeAreaViewInstance, // 21dba39c
ScaledSize, // 07e417c7
ScrollEvent, // d7abdd0a
ScrollResponderType, // 079145bb
ScrollResponderType, // ba188eae
ScrollToLocationParamsType, // d7ecdad1
ScrollView, // d7defbe2
ScrollViewImperativeMethods, // d3ff1532
ScrollViewInstance, // 1ba64600
ScrollViewProps, // 9207390b
ScrollViewPropsAndroid, // 44210553
ScrollViewPropsIOS, // da991b9a
ScrollView, // 066a8597
ScrollViewImperativeMethods, // 904c66fd
ScrollViewInstance, // ccf4f341
ScrollViewProps, // b62913d1
ScrollViewPropsAndroid, // 02f3df2e
ScrollViewPropsIOS, // 807cb4f6
ScrollViewScrollToOptions, // 3313411e
SectionBase, // dca83594
SectionList, // f483c886
SectionListData, // e0d79987
SectionListInstance, // 1b255509
SectionListProps, // e06b9868
SectionListRenderItem, // 466e3e87
SectionListRenderItemInfo, // d809238e
SectionBase, // 9f13db00
SectionList, // ee3e7972
SectionListData, // 1a4de01a
SectionListInstance, // c9b991fe
SectionListProps, // 0e933318
SectionListRenderItem, // 715b2086
SectionListRenderItemInfo, // 4a48a922
Separators, // 6a45f7e3
Settings, // 2be0c61e
Share, // e4591b32
@@ -6091,72 +6091,72 @@ export {
ShareActionSheetIOSOptions, // eff574f5
ShareContent, // 7c627896
ShareOptions, // 800c3a4e
StatusBar, // 81b4e92f
StatusBar, // f95dbb76
StatusBarAnimation, // 7fd047e6
StatusBarInstance, // 4e03048c
StatusBarProps, // bfedeab1
StatusBarInstance, // 416e9640
StatusBarProps, // c2a44d88
StatusBarStyle, // 78f53eea
StyleProp, // fa0e9b4a
StyleSheet, // cdddb9ae
StyleSheet, // e734acd4
SubmitBehavior, // c4ddf490
Switch, // b004beeb
Switch, // f495bab3
SwitchChangeEvent, // 899635b1
SwitchInstance, // 3c50eec5
SwitchProps, // ded81873
SwitchProps, // cadfd0c4
Systrace, // 626d178c
TVViewPropsIOS, // 330ce7b5
TargetedEvent, // 16e98910
TaskProvider, // 266dedf2
Text, // f792e51d
Text, // 9ceaff08
TextContentType, // 239b3ecc
TextInput, // 4d0a088b
TextInputAndroidProps, // 7109938a
TextInput, // ce1e41ff
TextInputAndroidProps, // 9ebbc103
TextInputBlurEvent, // b77af40e
TextInputChangeEvent, // f55eef98
TextInputContentSizeChangeEvent, // a27cd32a
TextInputEndEditingEvent, // e690b56b
TextInputFocusEvent, // 19851a88
TextInputIOSProps, // 0d05a855
TextInputIOSProps, // fb3c9327
TextInputInstance, // 5a0c0e0d
TextInputKeyPressEvent, // 546c5d07
TextInputProps, // a56c62a0
TextInputProps, // c692f4de
TextInputSelectionChangeEvent, // e58f2abc
TextInputSubmitEditingEvent, // 6bcb2aa5
TextInstance, // 05463a96
TextLayoutEvent, // 3f54186f
TextProps, // 58466ea1
TextProps, // 93309fac
TextStyle, // b62b8399
ToastAndroid, // 88a8969a
TouchableHighlight, // 20ba0199
TouchableHighlight, // 3a6eaed4
TouchableHighlightInstance, // b510c0eb
TouchableHighlightProps, // 337a9164
TouchableNativeFeedback, // c41fc93f
TouchableNativeFeedbackInstance, // e7895451
TouchableNativeFeedbackProps, // a645b648
TouchableOpacity, // 74a1b2c6
TouchableHighlightProps, // 00e100c4
TouchableNativeFeedback, // fe739e82
TouchableNativeFeedbackInstance, // 0877e3e4
TouchableNativeFeedbackProps, // 1d0c2f30
TouchableOpacity, // fe716ffc
TouchableOpacityInstance, // b186055b
TouchableOpacityProps, // 1d3512ce
TouchableWithoutFeedback, // 61c63ada
TouchableWithoutFeedbackProps, // f4d62822
TouchableOpacityProps, // a3b32b69
TouchableWithoutFeedback, // ed159ba7
TouchableWithoutFeedbackProps, // e613ed05
TransformsStyle, // 65e70f18
TurboModule, // dfe29706
TurboModuleRegistry, // 4ace6db2
UIManager, // a1a7cc01
UIManager, // afbcdf05
UTFSequence, // ad625158
Vibration, // 31e4bbf8
View, // 443391a0
View, // 3e72139a
ViewInstance, // ffde5573
ViewProps, // 013135a2
ViewPropsAndroid, // 03c17367
ViewProps, // 763271bb
ViewPropsAndroid, // 55e81851
ViewPropsIOS, // 58ee19bf
ViewStyle, // e45056b1
VirtualViewMode, // 6be59722
VirtualizedList, // 68c7345e
VirtualizedListInstance, // 423ee7c0
VirtualizedListProps, // cc8bc4c9
VirtualizedListProps, // f51f4a42
VirtualizedSectionList, // 9fd9cd61
VirtualizedSectionListInstance, // 12b706d5
VirtualizedSectionListProps, // 12a58a4f
VirtualizedSectionListProps, // 8210f30a
WrapperComponentProvider, // 9ef54e61
codegenNativeCommands, // 628a7c0a
codegenNativeComponent, // 32a1bca6
@@ -6164,10 +6164,10 @@ export {
processColor, // 6e877698
registerCallableModule, // 839c8cfe
requireNativeComponent, // aa36a6dd
useAnimatedColor, // e3511f81
useAnimatedValue, // b18adb63
useAnimatedValueXY, // c7ee2332
useAnimatedColor, // a9dffd9f
useAnimatedValue, // 25733d13
useAnimatedValueXY, // 04c70878
useColorScheme, // d585efdb
usePressability, // 095343b5
usePressability, // 782138ed
useWindowDimensions, // bb4b683f
}
@@ -0,0 +1,65 @@
/**
* 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 strict-local
* @format
*/
const ensureUndefinedOnOptionalMembersVisitor = require('../ensureUndefinedOnOptionalMembers.js');
const babel = require('@babel/core');
async function translate(code: string): Promise<string> {
const result = await babel.transformAsync(code, {
plugins: [
'@babel/plugin-syntax-typescript',
ensureUndefinedOnOptionalMembersVisitor,
],
});
return result.code;
}
describe('ensureUndefinedOnOptionalMembers', () => {
test('should add undefined to optional type members', async () => {
const code = `
type Foo = {
a?: number,
'b-key'?: number | string,
c?: () => void,
d: boolean,
e: string | undefined,
f?: (() => void) | number,
};
`;
const result = await translate(code);
expect(result).toMatchInlineSnapshot(`
"type Foo = {
a?: number | undefined;
'b-key'?: number | string | undefined;
c?: (() => void) | undefined;
d: boolean;
e: string | undefined;
f?: (() => void) | number | undefined;
};"
`);
});
test('should not add undefined when already present', async () => {
const code = `
type Foo = {
a?: number | undefined,
b?: undefined,
};
`;
const result = await translate(code);
expect(result).toMatchInlineSnapshot(`
"type Foo = {
a?: number | undefined;
b?: undefined;
};"
`);
});
});
@@ -42,4 +42,20 @@ describe('removeUndefinedFromOptionalMembers', () => {
};"
`);
});
test('should unwrap the lone remaining constituent, dropping redundant parens', async () => {
const code = `
type Foo = {
a?: (() => void) | undefined,
b?: number | string | undefined,
};
`;
const result = await translate(code);
expect(result).toMatchInlineSnapshot(`
"type Foo = {
a?: () => void;
b?: number | string;
};"
`);
});
});
@@ -0,0 +1,71 @@
/**
* 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 strict-local
* @format
*/
import type {PluginObj} from '@babel/core';
import * as t from '@babel/types';
/**
* Ensures optional object members carry an explicit `| undefined`, e.g.
* `foo?: number` becomes `foo?: number | undefined` (members that already
* include `undefined` are left unchanged).
*
* This is the format required by TypeScript consumers building with
* `exactOptionalPropertyTypes`, under which `foo?: T` and `foo?: T | undefined`
* are distinct. It applies to the per-module generated types; the API snapshot
* pipeline still strips it back via `removeUndefinedFromOptionalMembers`.
*
* This lives in React Native as a stopgap: flow-api-translator does not yet emit
* this format, and the generated types cannot wait for its next release.
*
* The API snapshot resets this via `removeUndefinedFromOptionalMembers`, which
* also unwraps the resulting single-constituent unions so function-valued
* members remain bare function types for `sortProperties` to group.
*/
const visitor: PluginObj<unknown> = {
visitor: {
TSPropertySignature(path) {
if (path.node.optional !== true) {
return;
}
const typeAnnotation = path.node.typeAnnotation;
if (!typeAnnotation || !t.isTSTypeAnnotation(typeAnnotation)) {
return;
}
const actualTypeAnnotation = typeAnnotation.typeAnnotation;
if (t.isTSUndefinedKeyword(actualTypeAnnotation)) {
return;
}
if (t.isTSUnionType(actualTypeAnnotation)) {
if (
actualTypeAnnotation.types.some(type => t.isTSUndefinedKeyword(type))
) {
return;
}
typeAnnotation.typeAnnotation = t.tsUnionType([
...actualTypeAnnotation.types,
t.tsUndefinedKeyword(),
]);
return;
}
typeAnnotation.typeAnnotation = t.tsUnionType([
actualTypeAnnotation,
t.tsUndefinedKeyword(),
]);
},
},
};
module.exports = visitor;
@@ -29,11 +29,27 @@ const visitor: PluginObj<unknown> = {
return;
}
const newTypeAnnotation = t.cloneDeep(actualTypeAnnotation);
newTypeAnnotation.types = newTypeAnnotation.types.filter(
const remainingTypes = actualTypeAnnotation.types.filter(
type => t.isTSUndefinedKeyword(type) === false,
);
typeAnnotation.typeAnnotation = newTypeAnnotation;
if (remainingTypes.length === actualTypeAnnotation.types.length) {
return;
}
// Unwrap to the bare type when a single constituent remains, otherwise a
// wrapper node survives that prints identically but breaks downstream
// transforms (e.g. `sortProperties`) that key on the node type. The lone
// constituent may be parenthesized (a function type inside a union needs
// parens, e.g. `(() => void) | undefined`); those parens are redundant
// once it is no longer part of a union.
if (remainingTypes.length === 1) {
const [remaining] = remainingTypes;
typeAnnotation.typeAnnotation = t.isTSParenthesizedType(remaining)
? remaining.typeAnnotation
: remaining;
} else {
typeAnnotation.typeAnnotation = t.tsUnionType(remainingTypes);
}
},
},
};
@@ -30,6 +30,7 @@ const preTransforms: Array<PreTransformFn> = [
];
const postTransforms = (filePath: string): Array<PluginObj<unknown>> => [
require('./transforms/typescript/convertTypeAliasesToInterfaces'),
require('./transforms/typescript/ensureUndefinedOnOptionalMembers'),
require('./transforms/typescript/replaceProtectedConstructors'),
require('./transforms/typescript/replaceDefaultExportName')(filePath),
];