Support Array parsing in events (#37142)

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

This diff introduce support to parse arrays in events.

We support parsing Arrays of:
- Boolean
- Double
- Float
- Int32
- String Enums
- String
- Objects
- Arrays

## Changelog:
[General][Added] - Add generic support for Arrays in Events parsing

Reviewed By: dmytrorykun, RSNara

Differential Revision: D45268779

fbshipit-source-id: 0c6eae65eb2b41ebf7b47a4cc3e0f0e5fa20d871
This commit is contained in:
Riccardo Cipolleschi
2023-05-04 04:11:49 -07:00
committed by Facebook GitHub Bot
parent 6f75f14883
commit 6168701887
10 changed files with 14105 additions and 21 deletions
+5 -2
View File
@@ -113,7 +113,11 @@ export type EventTypeAnnotation =
| FloatTypeAnnotation
| Int32TypeAnnotation
| StringEnumTypeAnnotation
| ObjectTypeAnnotation<EventTypeAnnotation>;
| ObjectTypeAnnotation<EventTypeAnnotation>
| {
readonly type: 'ArrayTypeAnnotation';
readonly elementType: EventTypeAnnotation
};
export type PropTypeAnnotation =
| {
@@ -352,4 +356,3 @@ export type NativeModuleReturnOnlyTypeAnnotation =
| NativeModuleFunctionTypeAnnotation
| NativeModulePromiseTypeAnnotation
| VoidTypeAnnotation;
+5 -1
View File
@@ -128,7 +128,11 @@ export type EventTypeAnnotation =
| Int32TypeAnnotation
| MixedTypeAnnotation
| StringEnumTypeAnnotation
| ObjectTypeAnnotation<EventTypeAnnotation>;
| ObjectTypeAnnotation<EventTypeAnnotation>
| $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType: EventTypeAnnotation,
}>;
export type PropTypeAnnotation =
| $ReadOnly<{
@@ -177,6 +177,9 @@ function generateSetters(
typeAnnotation,
extraIncludes,
);
case 'ArrayTypeAnnotation':
// TODO: implement this in the next diff
break;
default:
(typeAnnotation.type: empty);
throw new Error(
@@ -133,6 +133,9 @@ function getNativeTypeFromAnnotation(
case 'StringEnumTypeAnnotation':
case 'ObjectTypeAnnotation':
return generateEventStructName([...nameParts, eventProperty.name]);
case 'ArrayTypeAnnotation':
// TODO: implement this in the next diff
return '';
default:
(type: empty);
throw new Error(`Received invalid event property type ${type}`);
@@ -205,6 +208,9 @@ function generateStruct(
case 'StringEnumTypeAnnotation':
generateEnum(structs, typeAnnotation.options, nameParts.concat([name]));
return;
case 'ArrayTypeAnnotation':
//TODO: implement this in the next diff
break;
default:
(typeAnnotation.type: empty);
throw new Error(
@@ -84,6 +84,57 @@ const EVENT_DEFINITION = `
object_readonly_optional_both?: ?$ReadOnly<{
int32_optional_both?: ?Int32,
}>,
boolean_array_required: $ReadOnlyArray<boolean>,
boolean_array_optional_key?: boolean[],
boolean_array_optional_value: ?$ReadOnlyArray<boolean>,
boolean_array_optional_both?: ?boolean[],
string_array_required: $ReadOnlyArray<string>,
string_array_optional_key?: string[],
string_array_optional_value: ?$ReadOnlyArray<string>,
string_array_optional_both?: ?string[],
double_array_required: $ReadOnlyArray<Double>,
double_array_optional_key?: Double[],
double_array_optional_value: ?$ReadOnlyArray<Double>,
double_array_optional_both?: ?Double[],
float_array_required: $ReadOnlyArray<Float>,
float_array_optional_key?: Float[],
float_array_optional_value: ?$ReadOnlyArray<Float>,
float_array_optional_both?: ?Float[],
int32_array_required: $ReadOnlyArray<Int32>,
int32_array_optional_key?: Int32[],
int32_array_optional_value: ?$ReadOnlyArray<Int32>,
int32_array_optional_both?: ?Int32[],
enum_array_required: $ReadOnlyArray<('small' | 'large')>,
enum_array_optional_key?: ('small' | 'large')[],
enum_array_optional_value: ?$ReadOnlyArray<('small' | 'large')>,
enum_array_optional_both?: ?('small' | 'large')[],
object_array_required: $ReadOnlyArray<{
boolean_required: boolean,
}>,
object_array_optional_key?: {
string_optional_key?: string,
}[],
object_array_optional_value: ?$ReadOnlyArray<{
float_optional_value: ?Float,
}>,
object_array_optional_both?: ?{
int32_optional_both?: ?Int32,
}[],
int32_array_array_required: $ReadOnlyArray<$ReadOnlyArray<Int32>>,
int32_array_array_optional_key?: Int32[][],
int32_array_array_optional_value: ?$ReadOnlyArray<$ReadOnlyArray<Int32>>,
int32_array_array_optional_both?: ?Int32[][],
`;
const ONE_OF_EACH_PROP_EVENT_DEFAULT_AND_OPTIONS = `
@@ -28,10 +28,7 @@ function getPropertyType(
optional: boolean,
typeAnnotation: $FlowFixMe,
): NamedShape<EventTypeAnnotation> {
const type =
typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotation.id.name
: typeAnnotation.type;
const type = extractTypeFromTypeAnnotation(typeAnnotation);
switch (type) {
case 'BooleanTypeAnnotation':
@@ -106,12 +103,87 @@ function getPropertyType(
type: 'MixedTypeAnnotation',
},
};
case 'ArrayTypeAnnotation':
case '$ReadOnlyArray':
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name),
};
default:
(type: empty);
throw new Error(`Unable to determine event type for "${name}": ${type}`);
}
}
function extractArrayElementType(
typeAnnotation: $FlowFixMe,
name: string,
): EventTypeAnnotation {
const type = extractTypeFromTypeAnnotation(typeAnnotation);
switch (type) {
case 'BooleanTypeAnnotation':
return {type: 'BooleanTypeAnnotation'};
case 'StringTypeAnnotation':
return {type: 'StringTypeAnnotation'};
case 'Int32':
return {type: 'Int32TypeAnnotation'};
case 'Float':
return {type: 'FloatTypeAnnotation'};
case 'NumberTypeAnnotation':
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'UnionTypeAnnotation':
return {
type: 'StringEnumTypeAnnotation',
options: typeAnnotation.types.map(option => option.value),
};
case 'UnsafeMixed':
return {type: 'MixedTypeAnnotation'};
case 'ObjectTypeAnnotation':
return {
type: 'ObjectTypeAnnotation',
properties: typeAnnotation.properties.map(buildPropertiesForEvent),
};
case 'ArrayTypeAnnotation':
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(typeAnnotation.elementType, name),
};
case '$ReadOnlyArray':
const genericParams = typeAnnotation.typeParameters.params;
if (genericParams.length !== 1) {
throw new Error(
`Events only supports arrays with 1 Generic type. Found ${
genericParams.length
} types:\n${prettify(genericParams)}`,
);
}
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(genericParams[0], name),
};
default:
throw new Error(
`Unrecognized ${type} for Array ${name} in events.\n${prettify(
typeAnnotation,
)}`,
);
}
}
function prettify(jsonObject: $FlowFixMe): string {
return JSON.stringify(jsonObject, null, 2);
}
function extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string {
return typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotation.id.name
: typeAnnotation.type;
}
function findEventArgumentsAndType(
parser: Parser,
typeAnnotation: $FlowFixMe,
@@ -219,24 +291,20 @@ function buildEventSchema(
const {argumentProps, bubblingType, paperTopLevelNameDeprecated} =
findEventArgumentsAndType(parser, typeAnnotation, types);
if (bubblingType && argumentProps) {
if (paperTopLevelNameDeprecated != null) {
return {
name,
optional,
bubblingType,
paperTopLevelNameDeprecated,
typeAnnotation: {
type: 'EventTypeAnnotation',
argument: getEventArgument(argumentProps, name),
},
};
}
if (!argumentProps) {
throw new Error(`Unable to determine event arguments for "${name}"`);
}
if (!bubblingType) {
throw new Error(`Unable to determine event arguments for "${name}"`);
}
if (paperTopLevelNameDeprecated != null) {
return {
name,
optional,
bubblingType,
paperTopLevelNameDeprecated,
typeAnnotation: {
type: 'EventTypeAnnotation',
argument: getEventArgument(argumentProps, name),
@@ -249,6 +317,16 @@ function buildEventSchema(
}
throwIfBubblingTypeIsNull(bubblingType, name);
return {
name,
optional,
bubblingType,
typeAnnotation: {
type: 'EventTypeAnnotation',
argument: getEventArgument(argumentProps, name),
},
};
}
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
@@ -84,6 +84,57 @@ const EVENT_DEFINITION = `
object_readonly_optional_both?: Readonly<{
int32_optional_both?: Int32 | null | undefined;
}> | null | undefined;
boolean_array_required: boolean[];
boolean_array_optional_key?: boolean[];
boolean_array_optional_value: boolean[] | null | undefined;
boolean_array_optional_both?: boolean[] | null | undefined;
string_array_required: string[];
string_array_optional_key?: (string[]);
string_array_optional_value: (string[]) | null | undefined;
string_array_optional_both?: (string[] | null | undefined);
double_array_required: Double[];
double_array_optional_key?: Double[];
double_array_optional_value: Double[] | null | undefined;
double_array_optional_both?: Double[] | null | undefined;
float_array_required: Float[];
float_array_optional_key?: Float[];
float_array_optional_value: Float[] | null | undefined;
float_array_optional_both?: Float[] | null | undefined;
int32_array_required: Int32[];
int32_array_optional_key?: Int32[];
int32_array_optional_value: Int32[] | null | undefined;
int32_array_optional_both?: Int32[] | null | undefined;
enum_array_required: ('small' | 'large')[];
enum_array_optional_key?: ('small' | 'large')[];
enum_array_optional_value: ('small' | 'large')[] | null | undefined;
enum_array_optional_both?: ('small' | 'large')[] | null | undefined;
object_array_required: {
boolean_required: boolean;
}[];
object_array_optional_key?: {
string_optional_key?: string;
}[];
object_array_optional_value: {
float_optional_value: Float | null | undefined;
}[] | null | undefined;
object_array_optional_both?: {
int32_optional_both?: Int32 | null | undefined;
}[] | null | undefined;
int32_array_array_required: Int32[][];
int32_array_array_optional_key?: Int32[][];
int32_array_array_optional_value: Int32[][] | null | undefined;
int32_array_array_optional_both?: Int32[][] | null | undefined;
`;
const ONE_OF_EACH_PROP_EVENT_DEFAULT_AND_OPTIONS = `
@@ -108,12 +108,76 @@ function getPropertyType(
type: 'MixedTypeAnnotation',
},
};
case 'TSArrayType':
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name),
};
default:
(type: empty);
throw new Error(`Unable to determine event type for "${name}": ${type}`);
}
}
function extractArrayElementType(
typeAnnotation: $FlowFixMe,
name: string,
): EventTypeAnnotation {
const type = extractTypeFromTypeAnnotation(typeAnnotation);
switch (type) {
case 'TSParenthesizedType':
return extractArrayElementType(typeAnnotation.typeAnnotation, name);
case 'TSBooleanKeyword':
return {type: 'BooleanTypeAnnotation'};
case 'TSStringKeyword':
return {type: 'StringTypeAnnotation'};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'TSNumberKeyword':
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'TSUnionType':
return {
type: 'StringEnumTypeAnnotation',
options: typeAnnotation.types.map(option => option.literal.value),
};
case 'TSTypeLiteral':
return {
type: 'ObjectTypeAnnotation',
properties: typeAnnotation.members.map(buildPropertiesForEvent),
};
case 'TSArrayType':
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(typeAnnotation.elementType, name),
};
default:
throw new Error(
`Unrecognized ${type} for Array ${name} in events.\n${JSON.stringify(
typeAnnotation,
null,
2,
)}`,
);
}
}
function extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string {
return typeAnnotation.type === 'TSTypeReference'
? typeAnnotation.typeName.name
: typeAnnotation.type;
}
function findEventArgumentsAndType(
parser: Parser,
typeAnnotation: $FlowFixMe,