Skip to content

Commit

Permalink
Enforce compatibility with exactOptionalPropertyTypes (#36345)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #36345

`exactOptionalPropertyTypes` is a TypeScript 4.4+ option set by users which changes behavior of optional properties, to disable accepting explicit `undefined`.

This is not enabled when using `--strict`, and is stricter than Flow, leading to most of the typings having an `| undefined` unique to TypeScript (added with DefinitelyTyped/DefinitelyTyped@694c663).

We have not always followed this (I have myself previously assumed the two are equivalent). We can enforce that the convention is followed with a plugin `eslint-plugin-redundant-undefined`. This forces us to declare that every optional property accepts an explicit undefined (which Flow would allow). Alternatively, if we do not want to support this, we can enable the existing dtslint rule `no-redundant-undefined`.

Changelog:
[General][Fixed] - Enforce compatibility with `exactOptionalPropertyTypes`

Reviewed By: lunaleaps

Differential Revision: D43700862

fbshipit-source-id: 996094762b28918177521a9471d868ba87f0f263
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Mar 8, 2023
1 parent f4e56fd commit 7858a21
Show file tree
Hide file tree
Showing 27 changed files with 140 additions and 68 deletions.
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,15 @@ module.exports = {
'react/self-closing-comp': 'off',
},
},
{
files: ['**/*.d.ts'],
plugins: ['redundant-undefined'],
rules: {
'redundant-undefined/redundant-undefined': [
'error',
{followExactOptionalPropertyTypes: true},
],
},
},
],
};
4 changes: 2 additions & 2 deletions Libraries/Alert/Alert.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
export interface AlertButton {
text?: string | undefined;
onPress?: ((value?: string) => void) | undefined;
isPreferred?: boolean;
isPreferred?: boolean | undefined;
style?: 'default' | 'cancel' | 'destructive' | undefined;
}

interface AlertOptions {
/** @platform android */
cancelable?: boolean | undefined;
userInterfaceStyle?: 'unspecified' | 'light' | 'dark';
userInterfaceStyle?: 'unspecified' | 'light' | 'dark' | undefined;
/** @platform android */
onDismiss?: (() => void) | undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Animated/Animated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ export namespace Animated {
extends React.FC<AnimatedProps<React.ComponentPropsWithRef<T>>> {}

export type AnimatedComponentOptions = {
collapsable?: boolean;
collapsable?: boolean | undefined;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface AccessibilityInfoStatic {
*/
announceForAccessibilityWithOptions(
announcement: string,
options: {queue?: boolean},
options: {queue?: boolean | undefined},
): void;

/**
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Components/Pressable/Pressable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export interface PressableProps
/**
* Duration (in milliseconds) to wait after press down before calling onPressIn.
*/
unstable_pressDelay?: number;
unstable_pressDelay?: number | undefined;
}

// TODO use React.AbstractComponent when available
Expand Down
4 changes: 2 additions & 2 deletions Libraries/Components/ScrollView/ScrollView.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ export interface ScrollViewProps
/**
* When true, Sticky header is hidden when scrolling down, and dock at the top when scrolling up.
*/
stickyHeaderHiddenOnScroll?: boolean;
stickyHeaderHiddenOnScroll?: boolean | undefined;

/**
* Style
Expand Down Expand Up @@ -841,7 +841,7 @@ export class ScrollView extends ScrollViewBase {
* The options object has an animated prop, that enables the scrolling animation or not.
* The animated prop defaults to true
*/
scrollToEnd(options?: {animated?: boolean}): void;
scrollToEnd(options?: {animated?: boolean | undefined}): void;

/**
* Displays the scroll indicators momentarily.
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Components/TextInput/InputAccessoryView.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class InputAccessoryView extends React.Component<InputAccessoryViewProps>
export interface InputAccessoryViewProps {
backgroundColor?: ColorValue | undefined;

children?: React.ReactNode;
children?: React.ReactNode | undefined;

/**
* An ID which is used to associate this InputAccessoryView to specified TextInput(s).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface TouchableWithoutFeedbackProps
extends TouchableWithoutFeedbackPropsIOS,
TouchableWithoutFeedbackPropsAndroid,
AccessibilityProps {
children?: React.ReactNode;
children?: React.ReactNode | undefined;

/**
* Delay in ms, from onPressIn, before onLongPress is called.
Expand Down
10 changes: 5 additions & 5 deletions Libraries/Components/View/ViewAccessibility.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ export interface AccessibilityProps
*/
accessibilityValue?: AccessibilityValue | undefined;

'aria-valuemax'?: AccessibilityValue['max'];
'aria-valuemin'?: AccessibilityValue['min'];
'aria-valuenow'?: AccessibilityValue['now'];
'aria-valuetext'?: AccessibilityValue['text'];
'aria-valuemax'?: AccessibilityValue['max'] | undefined;
'aria-valuemin'?: AccessibilityValue['min'] | undefined;
'aria-valuenow'?: AccessibilityValue['now'] | undefined;
'aria-valuetext'?: AccessibilityValue['text'] | undefined;
/**
* When `accessible` is true, the system will try to invoke this function when the user performs an accessibility custom action.
*/
Expand All @@ -105,7 +105,7 @@ export interface AccessibilityProps
/**
* Indicates to accessibility services to treat UI component like a specific role.
*/
role?: Role;
role?: Role | undefined;
}

export type AccessibilityActionInfo = Readonly<{
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Components/View/ViewPropTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export interface ViewProps
Touchable,
PointerEvents,
AccessibilityProps {
children?: React.ReactNode;
children?: React.ReactNode | undefined;
/**
* This defines how far a touch event can start away from the view.
* Typical interface guidelines recommend touch targets that are at least
Expand Down
4 changes: 2 additions & 2 deletions Libraries/Image/Image.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export interface ImagePropsBase
*
* See https://reactnative.dev/docs/image#crossorigin
*/
crossOrigin?: 'anonymous' | 'use-credentials';
crossOrigin?: 'anonymous' | 'use-credentials' | undefined;

/**
* Changes the color of all the non-transparent pixels to the tintColor.
Expand Down Expand Up @@ -359,7 +359,7 @@ export class Image extends ImageBase {
}

export interface ImageBackgroundProps extends ImagePropsBase {
children?: React.ReactNode;
children?: React.ReactNode | undefined;
imageStyle?: StyleProp<ImageStyle> | undefined;
style?: StyleProp<ViewStyle> | undefined;
imageRef?(image: Image): void;
Expand Down
4 changes: 2 additions & 2 deletions Libraries/Lists/FlatList.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
* If any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the `data` prop,
* stick it here and treat it immutably.
*/
extraData?: any;
extraData?: any | undefined;

/**
* `getItemLayout` is an optional optimization that lets us skip measurement of dynamic
Expand Down Expand Up @@ -144,7 +144,7 @@ export interface FlatListProps<ItemT> extends VirtualizedListProps<ItemT> {
/**
* See `ViewabilityHelper` for flow type and further documentation.
*/
viewabilityConfig?: any;
viewabilityConfig?: any | undefined;

/**
* Note: may have bugs (missing content) in some circumstances - use at your own risk.
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Lists/SectionList.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export interface SectionListProps<ItemT, SectionT = DefaultSectionT>
* If any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the `data` prop,
* stick it here and treat it immutably.
*/
extraData?: any;
extraData?: any | undefined;

/**
* `getItemLayout` is an optional optimization that lets us skip measurement of dynamic
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Text/Text.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export interface TextProps
*/
allowFontScaling?: boolean | undefined;

children?: React.ReactNode;
children?: React.ReactNode | undefined;

/**
* This can be one of the following values:
Expand Down
8 changes: 4 additions & 4 deletions Libraries/Utilities/createPerformanceLogger.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

export type Timespan = {
startTime: number;
endTime?: number;
totalTime?: number;
startExtras?: Extras;
endExtras?: Extras;
endTime?: number | undefined;
totalTime?: number | undefined;
startExtras?: Extras | undefined;
endExtras?: Extras | undefined;
};

// Extra values should be serializable primitives
Expand Down
20 changes: 10 additions & 10 deletions packages/react-native-codegen/src/CodegenSchema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface VoidTypeAnnotation {
export interface ObjectTypeAnnotation<T> {
readonly type: 'ObjectTypeAnnotation';
readonly properties: readonly NamedShape<T>[];
readonly baseTypes?: readonly string[];
readonly baseTypes?: readonly string[] | undefined;
}

export interface FunctionTypeAnnotation<P, R> {
Expand Down Expand Up @@ -77,10 +77,10 @@ export interface ComponentShape extends OptionsShape {
}

export interface OptionsShape {
readonly interfaceOnly?: boolean;
readonly paperComponentName?: string;
readonly excludedPlatforms?: readonly PlatformType[];
readonly paperComponentNameDeprecated?: string;
readonly interfaceOnly?: boolean | undefined;
readonly paperComponentName?: string | undefined;
readonly excludedPlatforms?: readonly PlatformType[] | undefined;
readonly paperComponentNameDeprecated?: string | undefined;
}

export interface ExtendsPropsShape {
Expand All @@ -94,10 +94,10 @@ export interface EventTypeShape {
| 'direct'
| 'bubble';
readonly optional: boolean;
readonly paperTopLevelNameDeprecated?: string;
readonly paperTopLevelNameDeprecated?: string | undefined;
readonly typeAnnotation: {
readonly type: 'EventTypeAnnotation';
readonly argument?: ObjectTypeAnnotation<EventTypeAnnotation>;
readonly argument?: ObjectTypeAnnotation<EventTypeAnnotation> | undefined;
};
}

Expand Down Expand Up @@ -211,7 +211,7 @@ export interface NativeModuleSchema {
readonly enumMap: NativeModuleEnumMap;
readonly spec: NativeModuleSpec;
readonly moduleName: string;
readonly excludedPlatforms?: readonly PlatformType[];
readonly excludedPlatforms?: readonly PlatformType[] | undefined;
}

export interface NativeModuleSpec {
Expand All @@ -234,7 +234,7 @@ export type NativeModuleObjectTypeAnnotation = ObjectTypeAnnotation<Nullable<Nat

export interface NativeModuleArrayTypeAnnotation<T extends Nullable<NativeModuleBaseTypeAnnotation>> {
readonly type: 'ArrayTypeAnnotation';
readonly elementType?: T;
readonly elementType?: T | undefined;
}

export interface NativeModuleStringTypeAnnotation {
Expand Down Expand Up @@ -294,7 +294,7 @@ export interface NativeModuleTypeAliasTypeAnnotation {

export interface NativeModulePromiseTypeAnnotation {
readonly type: 'PromiseTypeAnnotation';
readonly elementType?: Nullable<NativeModuleBaseTypeAnnotation>;
readonly elementType?: Nullable<NativeModuleBaseTypeAnnotation> | undefined;
}

export type UnionTypeAnnotationMemberType =
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-codegen/src/SchemaValidator.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import type { SchemaType } from "./CodegenSchema";
import type { SchemaType } from './CodegenSchema';

export declare function getErrors(schema: SchemaType): readonly string[];
export declare function validate(schema: SchemaType): void;
4 changes: 2 additions & 2 deletions packages/react-native-codegen/src/parsers/parser.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

import type { SchemaType } from "../CodegenSchema";
import type { ParserType } from "./errors";
import type { SchemaType } from '../CodegenSchema';
import type { ParserType } from './errors';

// useful members only for downstream
export interface Parser {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/

import type { SchemaType } from "../../CodegenSchema";
import type { SchemaType } from '../../CodegenSchema';

export declare function parse(filename: string): SchemaType | undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,10 @@ export function SectionList_scrollable(Props: {
ref={ref}
ListHeaderComponent={HeaderComponent}
ListFooterComponent={FooterComponent}
// eslint-disable-next-line react/no-unstable-nested-components
// $FlowFixMe[missing-local-annot]
SectionSeparatorComponent={info => (
<CustomSeparatorComponent {...info} text="SECTION SEPARATOR" />
)}
// eslint-disable-next-line react/no-unstable-nested-components
// $FlowFixMe[missing-local-annot]
ItemSeparatorComponent={info => (
<CustomSeparatorComponent {...info} text="ITEM SEPARATOR" />
Expand Down
6 changes: 3 additions & 3 deletions packages/virtualized-lists/Lists/VirtualizedList.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface ViewToken {
key: string;
index: number | null;
isViewable: boolean;
section?: any;
section?: any | undefined;
}

export interface ViewabilityConfig {
Expand Down Expand Up @@ -188,7 +188,7 @@ export interface VirtualizedListWithoutRenderItemProps<ItemT>
* The default accessor functions assume this is an Array<{key: string}> but you can override
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
*/
data?: any;
data?: any | undefined;

/**
* `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
Expand All @@ -208,7 +208,7 @@ export interface VirtualizedListWithoutRenderItemProps<ItemT>
* any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the
* `data` prop, stick it here and treat it immutably.
*/
extraData?: any;
extraData?: any | undefined;

/**
* A generic accessor for extracting an item from any sort of data blob.
Expand Down
1 change: 1 addition & 0 deletions repo-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-native": "^4.0.0",
"eslint-plugin-redundant-undefined": "^0.4.0",
"eslint-plugin-relay": "^1.8.3",
"flow-bin": "^0.201.0",
"inquirer": "^7.1.0",
Expand Down
20 changes: 10 additions & 10 deletions types/__typetests__/fabric-component-sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ type Event = Readonly<{
}>;

interface NativeProps extends ViewProps {
string?: string;
number?: number;
boolean?: boolean;
default?: WithDefault<'option1' | 'option2', 'option1'>;
double?: Double;
float?: Float;
int32?: Int32;
unsafeObject?: UnsafeObject;
onBubblingEventHandler?: BubblingEventHandler<Event>;
onDirectEventHandler?: DirectEventHandler<Event>;
string?: string | undefined;
number?: number | undefined;
boolean?: boolean | undefined;
default?: WithDefault<'option1' | 'option2', 'option1'> | undefined;
double?: Double | undefined;
float?: Float | undefined;
int32?: Int32 | undefined;
unsafeObject?: UnsafeObject | undefined;
onBubblingEventHandler?: BubblingEventHandler<Event> | undefined;
onDirectEventHandler?: DirectEventHandler<Event> | undefined;
}

export type SampleViewType = NativeComponentType<NativeProps>;
Expand Down
8 changes: 4 additions & 4 deletions types/modules/Codegen.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ declare module 'react-native/Libraries/Utilities/codegenNativeComponent' {
import type {HostComponent} from 'react-native';

export interface Options {
readonly interfaceOnly?: boolean;
readonly paperComponentName?: string;
readonly paperComponentNameDeprecated?: string;
readonly excludedPlatforms?: ReadonlyArray<'iOS' | 'android'>;
readonly interfaceOnly?: boolean | undefined;
readonly paperComponentName?: string | undefined;
readonly paperComponentNameDeprecated?: string | undefined;
readonly excludedPlatforms?: ReadonlyArray<'iOS' | 'android'> | undefined;
}

export type NativeComponentType<T> = HostComponent<T>;
Expand Down

0 comments on commit 7858a21

Please sign in to comment.