Enforce compatibility with exactOptionalPropertyTypes (#36345)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/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 https://github.com/DefinitelyTyped/DefinitelyTyped/commit/694c663a9486dbe7794d5eb894a691ee9ded318a).

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
This commit is contained in:
Nick Gerleman
2023-03-08 00:14:56 -08:00
committed by Facebook GitHub Bot
parent f4e56fdf19
commit 7858a2147f
27 changed files with 140 additions and 68 deletions
+10
View File
@@ -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},
],
},
},
],
};
+2 -2
View File
@@ -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;
}
+1 -1
View File
@@ -579,7 +579,7 @@ export namespace Animated {
extends React.FC<AnimatedProps<React.ComponentPropsWithRef<T>>> {}
export type AnimatedComponentOptions = {
collapsable?: boolean;
collapsable?: boolean | undefined;
};
/**
@@ -137,7 +137,7 @@ export interface AccessibilityInfoStatic {
*/
announceForAccessibilityWithOptions(
announcement: string,
options: {queue?: boolean},
options: {queue?: boolean | undefined},
): void;
/**
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
@@ -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.
+1 -1
View File
@@ -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).
@@ -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.
+5 -5
View File
@@ -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.
*/
@@ -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<{
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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.
@@ -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;
+2 -2
View File
@@ -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
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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:
+4 -4
View File
@@ -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
+10 -10
View File
@@ -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> {
@@ -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 {
@@ -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;
};
}
@@ -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 {
@@ -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 {
@@ -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 =
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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 {
@@ -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;
@@ -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" />
+3 -3
View File
@@ -23,7 +23,7 @@ export interface ViewToken {
key: string;
index: number | null;
isViewable: boolean;
section?: any;
section?: any | undefined;
}
export interface ViewabilityConfig {
@@ -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
@@ -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.
+1
View File
@@ -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",
+10 -10
View File
@@ -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>;
+4 -4
View File
@@ -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>;
+11 -9
View File
@@ -80,7 +80,9 @@ declare var Blob: {
new (blobParts?: Array<Blob | string>, options?: BlobOptions): Blob;
};
type FormDataValue = string | {name?: string; type?: string; uri: string};
type FormDataValue =
| string
| {name?: string | undefined; type?: string | undefined; uri: string};
type FormDataPart =
| {
@@ -90,8 +92,8 @@ type FormDataPart =
| {
uri: string;
headers: {[name: string]: string};
name?: string;
type?: string;
name?: string | undefined;
type?: string | undefined;
};
declare class FormData {
@@ -160,7 +162,7 @@ declare interface RequestInit {
method?: string | undefined;
mode?: RequestMode_ | undefined;
referrer?: string | undefined;
window?: any;
window?: any | undefined;
signal?: AbortSignal | undefined;
}
@@ -409,7 +411,7 @@ declare class URLSearchParams {
}
interface WebSocketMessageEvent extends Event {
data?: any;
data?: any | undefined;
}
interface WebSocketErrorEvent extends Event {
message: string;
@@ -493,9 +495,9 @@ declare class AbortSignal implements EventTarget {
options?:
| boolean
| {
capture?: boolean;
once?: boolean;
passive?: boolean;
capture?: boolean | undefined;
once?: boolean | undefined;
passive?: boolean | undefined;
},
) => void;
@@ -505,7 +507,7 @@ declare class AbortSignal implements EventTarget {
options?:
| boolean
| {
capture?: boolean;
capture?: boolean | undefined;
},
) => void;
}
+2 -1
View File
@@ -18,6 +18,7 @@
"strict-export-declare-modifiers": false,
"whitespace": false,
"use-default-type-parameter": false,
"no-redundant-jsdoc": false
"no-redundant-jsdoc": false,
"no-any-union": false
}
}
+60
View File
@@ -2622,6 +2622,11 @@
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
"@types/semver@^7.3.12":
version "7.3.13"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==
"@types/stack-utils@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
@@ -2693,6 +2698,14 @@
"@typescript-eslint/types" "5.37.0"
"@typescript-eslint/visitor-keys" "5.37.0"
"@typescript-eslint/scope-manager@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz#74b28ac9a3fc8166f04e806c957adb8c1fd00536"
integrity sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==
dependencies:
"@typescript-eslint/types" "5.54.0"
"@typescript-eslint/visitor-keys" "5.54.0"
"@typescript-eslint/type-utils@5.37.0":
version "5.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz#43ed2f567ada49d7e33a6e4b6f9babd060445fe5"
@@ -2708,6 +2721,11 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.37.0.tgz#09e4870a5f3af7af3f84e08d792644a87d232261"
integrity sha512-3frIJiTa5+tCb2iqR/bf7XwU20lnU05r/sgPJnRpwvfZaqCJBrl8Q/mw9vr3NrNdB/XtVyMA0eppRMMBqdJ1bA==
"@typescript-eslint/types@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.54.0.tgz#7d519df01f50739254d89378e0dcac504cab2740"
integrity sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==
"@typescript-eslint/typescript-estree@5.37.0":
version "5.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.37.0.tgz#956dcf5c98363bcb97bdd5463a0a86072ff79355"
@@ -2721,6 +2739,19 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.0.tgz#f6f3440cabee8a43a0b25fa498213ebb61fdfe99"
integrity sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==
dependencies:
"@typescript-eslint/types" "5.54.0"
"@typescript-eslint/visitor-keys" "5.54.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.37.0", "@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.30.0":
version "5.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.37.0.tgz#7784cb8e91390c4f90ccaffd24a0cf9874df81b2"
@@ -2733,6 +2764,20 @@
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/utils@^5.47.1":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.54.0.tgz#3db758aae078be7b54b8ea8ea4537ff6cd3fbc21"
integrity sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==
dependencies:
"@types/json-schema" "^7.0.9"
"@types/semver" "^7.3.12"
"@typescript-eslint/scope-manager" "5.54.0"
"@typescript-eslint/types" "5.54.0"
"@typescript-eslint/typescript-estree" "5.54.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
semver "^7.3.7"
"@typescript-eslint/visitor-keys@5.37.0":
version "5.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.37.0.tgz#7b72dd343295ea11e89b624995abc7103c554eee"
@@ -2741,6 +2786,14 @@
"@typescript-eslint/types" "5.37.0"
eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@5.54.0":
version "5.54.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.0.tgz#846878afbf0cd67c19cfa8d75947383d4490db8f"
integrity sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==
dependencies:
"@typescript-eslint/types" "5.54.0"
eslint-visitor-keys "^3.3.0"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -4145,6 +4198,13 @@ eslint-plugin-react@^7.30.1:
semver "^6.3.0"
string.prototype.matchall "^4.0.7"
eslint-plugin-redundant-undefined@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-redundant-undefined/-/eslint-plugin-redundant-undefined-0.4.0.tgz#1445467d1920809f13f965f486a2d59f14f4aa65"
integrity sha512-+nzoUIWJCIwlWxhoFL9LJDPE2y0RCq2Eq8qD8NhfMScTHlNHvBc0haIWdBkjgzJZaN6OX/bkke/4WisV0qs7Vg==
dependencies:
"@typescript-eslint/utils" "^5.47.1"
eslint-plugin-relay@^1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-relay/-/eslint-plugin-relay-1.8.3.tgz#3f320b79259c14c8112909568b208a08e7a9ebae"