Support TypeScript array types for turbo module (module only) (#34183)

Summary:
Turbo module codegen supports arrays in both Flow and TypeScript, but it only recognize `Array<T>` and `ReadonlyArray<T>` in TypeScript.

In this change, `T[]` and `readonly T[]` are made recognizable in codegen.

## Changelog

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

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

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

Reviewed By: lunaleaps

Differential Revision: D37812638

Pulled By: cipolleschi

fbshipit-source-id: d63b0585497a43c274d50e1877baab5d1cc3f8fa
This commit is contained in:
Zihan Chen (MSFT) 2022-07-13 15:46:44 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 89f090016d
Коммит f0c4c291e1
3 изменённых файлов: 417 добавлений и 65 удалений

Просмотреть файл

@ -322,6 +322,27 @@ export interface Spec extends TurboModule {
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_BASIC_ARRAY2 = `
/**
* 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
*/
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
readonly getArray: (arg: string[]) => string[];
readonly getArray: (arg: readonly string[]) => readonly string[];
}
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@ -377,6 +398,28 @@ export interface Spec extends TurboModule {
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE = `
/**
* 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
*/
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
readonly getArray: (
arg: [string, string][],
) => (string | number | boolean)[];
}
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_ARRAY_WITH_ALIAS = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@ -399,6 +442,28 @@ export interface Spec extends TurboModule {
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_ARRAY2_WITH_ALIAS = `
/**
* 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
*/
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export type SomeString = string;
export interface Spec extends TurboModule {
readonly getArray: (arg: SomeString[]) => string[];
}
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_COMPLEX_ARRAY = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@ -421,6 +486,28 @@ export interface Spec extends TurboModule {
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_COMPLEX_ARRAY2 = `
/**
* 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
*/
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
readonly getArray: (
arg: string[][][][][],
) => string[][][];
}
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
`;
const NATIVE_MODULE_WITH_PROMISE = `
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
@ -534,6 +621,7 @@ export default TurboModuleRegistry.getEnforcing<Spec>(
module.exports = {
NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY,
NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE,
NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE,
NATIVE_MODULE_WITH_FLOAT_AND_INT32,
NATIVE_MODULE_WITH_ALIASES,
NATIVE_MODULE_WITH_NESTED_ALIASES,
@ -545,8 +633,11 @@ module.exports = {
NATIVE_MODULE_WITH_ROOT_TAG,
NATIVE_MODULE_WITH_NULLABLE_PARAM,
NATIVE_MODULE_WITH_BASIC_ARRAY,
NATIVE_MODULE_WITH_BASIC_ARRAY2,
NATIVE_MODULE_WITH_COMPLEX_ARRAY,
NATIVE_MODULE_WITH_COMPLEX_ARRAY2,
NATIVE_MODULE_WITH_ARRAY_WITH_ALIAS,
NATIVE_MODULE_WITH_ARRAY2_WITH_ALIAS,
NATIVE_MODULE_WITH_BASIC_PARAM_TYPES,
NATIVE_MODULE_WITH_CALLBACK,
EMPTY_NATIVE_MODULE,

Просмотреть файл

@ -369,6 +369,49 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_AR
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY2_WITH_ALIAS 1`] = `
"{
'modules': {
'NativeSampleTurboModule': {
'type': 'NativeModule',
'aliases': {},
'spec': {
'properties': [
{
'name': 'getArray',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
},
'params': [
{
'name': 'arg',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
}
]
}
}
]
},
'moduleNames': [
'SampleTurboModule'
]
}
}
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE 1`] = `
"{
'modules': {
@ -406,6 +449,43 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_AR
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE 1`] = `
"{
'modules': {
'NativeSampleTurboModule': {
'type': 'NativeModule',
'aliases': {},
'spec': {
'properties': [
{
'name': 'getArray',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'ArrayTypeAnnotation'
},
'params': [
{
'name': 'arg',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation'
}
}
]
}
}
]
},
'moduleNames': [
'SampleTurboModule'
]
}
}
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_BASIC_ARRAY 1`] = `
"{
'modules': {
@ -474,6 +554,74 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_BA
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_BASIC_ARRAY2 1`] = `
"{
'modules': {
'NativeSampleTurboModule': {
'type': 'NativeModule',
'aliases': {},
'spec': {
'properties': [
{
'name': 'getArray',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
},
'params': [
{
'name': 'arg',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
}
]
}
},
{
'name': 'getArray',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
},
'params': [
{
'name': 'arg',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
}
]
}
}
]
},
'moduleNames': [
'SampleTurboModule'
]
}
}
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_BASIC_PARAM_TYPES 1`] = `
"{
'modules': {
@ -691,6 +839,67 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_CO
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ARRAY2 1`] = `
"{
'modules': {
'NativeSampleTurboModule': {
'type': 'NativeModule',
'aliases': {},
'spec': {
'properties': [
{
'name': 'getArray',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
}
},
'params': [
{
'name': 'arg',
'optional': false,
'typeAnnotation': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'ArrayTypeAnnotation',
'elementType': {
'type': 'StringTypeAnnotation'
}
}
}
}
}
}
}
]
}
}
]
},
'moduleNames': [
'SampleTurboModule'
]
}
}
}"
`;
exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_OBJECTS 1`] = `
"{
'modules': {

Просмотреть файл

@ -61,6 +61,82 @@ function nullGuard<T>(fn: () => T): ?T {
return fn();
}
function translateArrayTypeAnnotation(
hasteModuleName: string,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
cxxOnly: boolean,
tsArrayType: 'Array' | 'ReadonlyArray',
tsElementType: $FlowFixMe,
nullable: $FlowFixMe,
): Nullable<NativeModuleTypeAnnotation> {
try {
/**
* TODO(T72031674): Migrate all our NativeModule specs to not use
* invalid Array ElementTypes. Then, make the elementType a required
* parameter.
*/
const [elementType, isElementTypeNullable] = unwrapNullable(
translateTypeAnnotation(
hasteModuleName,
tsElementType,
types,
aliasMap,
/**
* TODO(T72031674): Ensure that all ParsingErrors that are thrown
* while parsing the array element don't get captured and collected.
* Why? If we detect any parsing error while parsing the element,
* we should default it to null down the line, here. This is
* the correct behaviour until we migrate all our NativeModule specs
* to be parseable.
*/
nullGuard,
cxxOnly,
),
);
if (elementType.type === 'VoidTypeAnnotation') {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
tsElementType,
tsArrayType,
'void',
);
}
if (elementType.type === 'PromiseTypeAnnotation') {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
tsElementType,
tsArrayType,
'Promise',
);
}
if (elementType.type === 'FunctionTypeAnnotation') {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
tsElementType,
tsArrayType,
'FunctionTypeAnnotation',
);
}
const finalTypeAnnotation: NativeModuleArrayTypeAnnotation<
Nullable<NativeModuleBaseTypeAnnotation>,
> = {
type: 'ArrayTypeAnnotation',
elementType: wrapNullable(isElementTypeNullable, elementType),
};
return wrapNullable(nullable, finalTypeAnnotation);
} catch (ex) {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
});
}
}
function translateTypeAnnotation(
hasteModuleName: string,
/**
@ -76,6 +152,38 @@ function translateTypeAnnotation(
resolveTypeAnnotation(typeScriptTypeAnnotation, types);
switch (typeAnnotation.type) {
case 'TSArrayType': {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
cxxOnly,
'Array',
typeAnnotation.elementType,
nullable,
);
}
case 'TSTypeOperator': {
if (
typeAnnotation.operator === 'readonly' &&
typeAnnotation.typeAnnotation.type === 'TSArrayType'
) {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
cxxOnly,
'ReadonlyArray',
typeAnnotation.typeAnnotation.elementType,
nullable,
);
} else {
throw new UnsupportedTypeScriptGenericParserError(
hasteModuleName,
typeAnnotation,
);
}
}
case 'TSTypeReference': {
switch (typeAnnotation.typeName.name) {
case 'RootTag': {
@ -101,71 +209,15 @@ function translateTypeAnnotation(
typeAnnotation,
);
try {
/**
* TODO(T72031674): Migrate all our NativeModule specs to not use
* invalid Array ElementTypes. Then, make the elementType a required
* parameter.
*/
const [elementType, isElementTypeNullable] = unwrapNullable(
translateTypeAnnotation(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
types,
aliasMap,
/**
* TODO(T72031674): Ensure that all ParsingErrors that are thrown
* while parsing the array element don't get captured and collected.
* Why? If we detect any parsing error while parsing the element,
* we should default it to null down the line, here. This is
* the correct behaviour until we migrate all our NativeModule specs
* to be parseable.
*/
nullGuard,
cxxOnly,
),
);
if (elementType.type === 'VoidTypeAnnotation') {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
typeAnnotation.type,
'void',
);
}
if (elementType.type === 'PromiseTypeAnnotation') {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
typeAnnotation.type,
'Promise',
);
}
if (elementType.type === 'FunctionTypeAnnotation') {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
typeAnnotation.type,
'FunctionTypeAnnotation',
);
}
const finalTypeAnnotation: NativeModuleArrayTypeAnnotation<
Nullable<NativeModuleBaseTypeAnnotation>,
> = {
type: 'ArrayTypeAnnotation',
elementType: wrapNullable(isElementTypeNullable, elementType),
};
return wrapNullable(nullable, finalTypeAnnotation);
} catch (ex) {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
});
}
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
cxxOnly,
typeAnnotation.type,
typeAnnotation.typeParameters.params[0],
nullable,
);
}
case 'Readonly': {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(