Support TypeScript array types for turbo module (component only) (#34216)

Summary:
Turbo module codegen for component parse `readonly T[]` and `readonly Object[][]` incorrectly. In this change, it is fixed, with necessary test cases added.

## Changelog

[General] [Added] - Support TypeScript array types for turbo module (component only)

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

Test Plan: `yarn jest` passed in `packages/react-native-codegen`

Reviewed By: sammy-SC

Differential Revision: D37953004

Pulled By: cipolleschi

fbshipit-source-id: b71ea35eb9c4bd5dc41130f1dcf9201704ec298e
This commit is contained in:
Zihan Chen (MSFT)
2022-09-14 15:28:33 +01:00
committed by Dmitry Rykun
parent 53da91c4e1
commit 2b3f6de630
3 changed files with 872 additions and 29 deletions
@@ -459,6 +459,138 @@ export default codegenNativeComponent<ModuleProps>(
) as HostComponent<ModuleProps>;
`;
const ARRAY2_PROP_TYPES_NO_EVENTS = `
/**
* 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.
*
* @format
*/
'use strict';
const codegenNativeComponent = require('codegenNativeComponent');
import type {Int32, Double, Float, WithDefault} from 'CodegenTypes';
import type {ImageSource} from 'ImageSource';
import type {
ColorValue,
ColorArrayValue,
PointValue,
EdgeInsetsValue,
} from 'StyleSheetTypes';
import type {ViewProps} from 'ViewPropTypes';
import type {HostComponent} from 'react-native';
type ObjectType = Readonly<{prop: string}>;
type ArrayObjectType = readonly Readonly<{prop: string}>[];
export interface ModuleProps extends ViewProps {
// Props
// Boolean props
array_boolean_required: readonly boolean[];
array_boolean_optional_key?: readonly boolean[];
array_boolean_optional_value: readonly boolean[] | null | undefined;
array_boolean_optional_both?: readonly boolean[] | null | undefined;
// String props
array_string_required: readonly string[];
array_string_optional_key?: readonly string[];
array_string_optional_value: readonly string[] | null | undefined;
array_string_optional_both?: readonly string[] | null | undefined;
// Double props
array_double_required: readonly Double[];
array_double_optional_key?: readonly Double[];
array_double_optional_value: readonly Double[] | null | undefined;
array_double_optional_both?: readonly Double[] | null | undefined;
// Float props
array_float_required: readonly Float[];
array_float_optional_key?: readonly Float[];
array_float_optional_value: readonly Float[] | null | undefined;
array_float_optional_both?: readonly Float[] | null | undefined;
// Int32 props
array_int32_required: readonly Int32[];
array_int32_optional_key?: readonly Int32[];
array_int32_optional_value: readonly Int32[] | null | undefined;
array_int32_optional_both?: readonly Int32[] | null | undefined;
// String enum props
array_enum_optional_key?: WithDefault<
readonly ('small' | 'large')[],
'small'
>;
array_enum_optional_both?: WithDefault<
readonly ('small' | 'large')[],
'small'
>;
// ImageSource props
array_image_required: readonly ImageSource[];
array_image_optional_key?: readonly ImageSource[];
array_image_optional_value: readonly ImageSource[] | null | undefined;
array_image_optional_both?: readonly ImageSource[] | null | undefined;
// ColorValue props
array_color_required: readonly ColorValue[];
array_color_optional_key?: readonly ColorValue[];
array_color_optional_value: readonly ColorValue[] | null | undefined;
array_color_optional_both?: readonly ColorValue[] | null | undefined;
// PointValue props
array_point_required: readonly PointValue[];
array_point_optional_key?: readonly PointValue[];
array_point_optional_value: readonly PointValue[] | null | undefined;
array_point_optional_both?: readonly PointValue[] | null | undefined;
// EdgeInsetsValue props
array_insets_required: readonly EdgeInsetsValue[];
array_insets_optional_key?: readonly EdgeInsetsValue[];
array_insets_optional_value: readonly EdgeInsetsValue[] | null | undefined;
array_insets_optional_both?: readonly EdgeInsetsValue[] | null | undefined;
// Object props
array_object_required: readonly Readonly<{prop: string}>[];
array_object_optional_key?: readonly Readonly<{prop: string}>[];
array_object_optional_value: ArrayObjectType | null | undefined;
array_object_optional_both?: readonly ObjectType[] | null | undefined;
// Nested array object types
array_of_array_object_required: readonly Readonly<{
// This needs to be the same name as the top level array above
array_object_required: readonly Readonly<{prop: string}>[];
}>[];
array_of_array_object_optional_key?: readonly Readonly<{
// This needs to be the same name as the top level array above
array_object_optional_key: readonly Readonly<{prop?: string}>[];
}>[];
array_of_array_object_optional_value: readonly Readonly<{
// This needs to be the same name as the top level array above
array_object_optional_value: readonly Readonly<{prop: string | null | undefined}>[];
}>[] | null | undefined;
array_of_array_object_optional_both?: readonly Readonly<{
// This needs to be the same name as the top level array above
array_object_optional_both: readonly Readonly<{prop?: string | null | undefined}>[];
}>[] | null | undefined;
// Nested array of array of object types
array_of_array_of_object_required: readonly Readonly<{
prop: string;
}>[][];
// Nested array of array of object types (in file)
array_of_array_of_object_required_in_file: readonly ObjectType[][];
}
export default codegenNativeComponent<ModuleProps>(
'Module',
) as HostComponent<ModuleProps>;
`;
const OBJECT_PROP_TYPES_NO_EVENTS = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -959,6 +1091,7 @@ export default codegenNativeComponent<ModuleProps>(
module.exports = {
ALL_PROP_TYPES_NO_EVENTS,
ARRAY_PROP_TYPES_NO_EVENTS,
ARRAY2_PROP_TYPES_NO_EVENTS,
OBJECT_PROP_TYPES_NO_EVENTS,
PROPS_ALIASED_LOCALLY,
ONE_OF_EACH_PROP_EVENT_DEFAULT_AND_OPTIONS,
@@ -1231,6 +1231,690 @@ exports[`RN Codegen TypeScript Parser can generate fixture ARRAY_PROP_TYPES_NO_E
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture ARRAY2_PROP_TYPES_NO_EVENTS 1`] = `
"{
'modules': {
'Module': {
'type': 'Component',
'components': {
'Module': {
'extendsProps': [
{
'type': 'ReactNativeBuiltInType',
'knownTypeName': 'ReactNativeCoreViewProps'
}
],
'events': [],
'props': [
{
'name': 'array_boolean_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'BooleanTypeAnnotation'
}
}
},
{
'name': 'array_boolean_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'BooleanTypeAnnotation'
}
}
},
{
'name': 'array_boolean_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'BooleanTypeAnnotation'
}
}
},
{
'name': 'array_boolean_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'BooleanTypeAnnotation'
}
}
},
{
'name': 'array_string_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
},
{
'name': 'array_string_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
},
{
'name': 'array_string_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
},
{
'name': 'array_string_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
},
{
'name': 'array_double_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'DoubleTypeAnnotation'
}
}
},
{
'name': 'array_double_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'DoubleTypeAnnotation'
}
}
},
{
'name': 'array_double_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'DoubleTypeAnnotation'
}
}
},
{
'name': 'array_double_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'DoubleTypeAnnotation'
}
}
},
{
'name': 'array_float_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'FloatTypeAnnotation'
}
}
},
{
'name': 'array_float_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'FloatTypeAnnotation'
}
}
},
{
'name': 'array_float_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'FloatTypeAnnotation'
}
}
},
{
'name': 'array_float_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'FloatTypeAnnotation'
}
}
},
{
'name': 'array_int32_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'Int32TypeAnnotation'
}
}
},
{
'name': 'array_int32_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'Int32TypeAnnotation'
}
}
},
{
'name': 'array_int32_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'Int32TypeAnnotation'
}
}
},
{
'name': 'array_int32_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'Int32TypeAnnotation'
}
}
},
{
'name': 'array_enum_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringEnumTypeAnnotation',
'default': 'small',
'options': [
'small',
'large'
]
}
}
},
{
'name': 'array_enum_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringEnumTypeAnnotation',
'default': 'small',
'options': [
'small',
'large'
]
}
}
},
{
'name': 'array_image_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ImageSourcePrimitive'
}
}
},
{
'name': 'array_image_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ImageSourcePrimitive'
}
}
},
{
'name': 'array_image_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ImageSourcePrimitive'
}
}
},
{
'name': 'array_image_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ImageSourcePrimitive'
}
}
},
{
'name': 'array_color_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ColorPrimitive'
}
}
},
{
'name': 'array_color_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ColorPrimitive'
}
}
},
{
'name': 'array_color_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ColorPrimitive'
}
}
},
{
'name': 'array_color_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'ColorPrimitive'
}
}
},
{
'name': 'array_point_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'PointPrimitive'
}
}
},
{
'name': 'array_point_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'PointPrimitive'
}
}
},
{
'name': 'array_point_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'PointPrimitive'
}
}
},
{
'name': 'array_point_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'PointPrimitive'
}
}
},
{
'name': 'array_insets_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'EdgeInsetsPrimitive'
}
}
},
{
'name': 'array_insets_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'EdgeInsetsPrimitive'
}
}
},
{
'name': 'array_insets_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'EdgeInsetsPrimitive'
}
}
},
{
'name': 'array_insets_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ReservedPropTypeAnnotation',
'name': 'EdgeInsetsPrimitive'
}
}
},
{
'name': 'array_object_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
},
{
'name': 'array_object_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
},
{
'name': 'array_object_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
},
{
'name': 'array_object_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
},
{
'name': 'array_of_array_object_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'array_object_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
}
]
}
}
},
{
'name': 'array_of_array_object_optional_key',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'array_object_optional_key',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': true,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
}
]
}
}
},
{
'name': 'array_of_array_object_optional_value',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'array_object_optional_value',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': true,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
}
]
}
}
},
{
'name': 'array_of_array_object_optional_both',
'optional': true,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'array_object_optional_both',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': true,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
}
]
}
}
},
{
'name': 'array_of_array_of_object_required',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
}
},
{
'name': 'array_of_array_of_object_required_in_file',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ObjectTypeAnnotation',
'properties': [
{
'name': 'prop',
'optional': false,
'typeAnnotation': {
'type': 'StringTypeAnnotation',
'default': null
}
}
]
}
}
}
}
],
'commands': []
}
}
}
}
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture COMMANDS_AND_EVENTS_TYPES_EXPORTED 1`] = `
"{
'modules': {
@@ -41,12 +41,54 @@ function getPropProperties(
}
}
function getTypeAnnotationForObjectAsArrayElement(
objectType: $FlowFixMe,
types: TypeDeclarationMap,
) {
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
objectType.typeParameters.params[0].members ||
objectType.typeParameters.params,
types,
)
.map(prop => buildPropSchema(prop, types))
.filter(Boolean),
};
}
function getTypeAnnotationForArrayOfArrayOfObject(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
) {
// We need to go yet another level deeper to resolve
// types that may be defined in a type alias
const nestedObjectType = getValueFromTypes(typeAnnotation, types);
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForObjectAsArrayElement(
nestedObjectType,
types,
),
};
}
function getTypeAnnotationForArray(
name: string,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe | null,
types: TypeDeclarationMap,
) {
if (typeAnnotation.type === 'TSParenthesizedType') {
return getTypeAnnotationForArray(
name,
typeAnnotation.typeAnnotation,
defaultValue,
types,
);
}
const extractedTypeAnnotation = getValueFromTypes(typeAnnotation, types);
if (
@@ -56,7 +98,7 @@ function getTypeAnnotationForArray(
)
) {
throw new Error(
'Nested optionals such as "ReadonlyArray<boolean | null | void>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
'Nested optionals such as "ReadonlyArray<boolean | null | undefined>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
);
}
@@ -69,44 +111,28 @@ function getTypeAnnotationForArray(
);
}
// Covers: T[]
if (typeAnnotation.type === 'TSArrayType') {
return getTypeAnnotationForArrayOfArrayOfObject(
typeAnnotation.elementType,
types,
);
}
if (extractedTypeAnnotation.type === 'TSTypeReference') {
// Resolve the type alias if it's not defined inline
const objectType = getValueFromTypes(extractedTypeAnnotation, types);
if (objectType.typeName.name === 'Readonly') {
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
objectType.typeParameters.params[0].members ||
objectType.typeParameters.params,
types,
)
.map(prop => buildPropSchema(prop, types))
.filter(Boolean),
};
return getTypeAnnotationForObjectAsArrayElement(objectType, types);
}
// Covers: ReadonlyArray<T>
if (objectType.typeName.name === 'ReadonlyArray') {
// We need to go yet another level deeper to resolve
// types that may be defined in a type alias
const nestedObjectType = getValueFromTypes(
return getTypeAnnotationForArrayOfArrayOfObject(
objectType.typeParameters.params[0],
types,
);
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
nestedObjectType.typeParameters.params[0].members ||
nestedObjectType.typeParameters.params,
types,
)
.map(prop => buildPropSchema(prop, types))
.filter(Boolean),
},
};
}
}
@@ -239,7 +265,7 @@ function getTypeAnnotation(
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeAnnotation,
typeAnnotation.typeAnnotation.elementType,
defaultValue,
types,
),