From b3848b7ec7cb507cb2c3ed3291a14b45b4df58aa Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" <53799235+ZihanChen-MSFT@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:05:55 -0700 Subject: [PATCH 1/6] Add TypeScript array test cases --- .../modules/__test_fixtures__/fixtures.js | 91 ++++++++ ...script-module-parser-snapshot-test.js.snap | 209 ++++++++++++++++++ 2 files changed, 300 insertions(+) diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js index 2d466d293d78b1..4530bfe08969a9 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js @@ -322,6 +322,27 @@ export interface Spec extends TurboModule { export default TurboModuleRegistry.getEnforcing('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('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('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('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('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('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('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('SampleTurboModule'); +`; + const NATIVE_MODULE_WITH_PROMISE = ` /** * Copyright (c) Meta Platforms, Inc. and affiliates. @@ -534,6 +621,7 @@ export default TurboModuleRegistry.getEnforcing( 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, diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap index 8a49cb12a3d0e2..20420a83361108 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap @@ -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': { From 00904821d26939d832f95509c5bc36a43e76273f Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" <53799235+ZihanChen-MSFT@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:12:35 -0700 Subject: [PATCH 2/6] Refactor --- .../src/parsers/typescript/modules/index.js | 140 ++++++++++-------- 1 file changed, 75 insertions(+), 65 deletions(-) diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/index.js b/packages/react-native-codegen/src/parsers/typescript/modules/index.js index a2048bbdc822d3..c0508433b8d90b 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -61,6 +61,77 @@ function nullGuard(fn: () => T): ?T { return fn(); } +function translateArrayTypeAnnotation( + tsArrayType: 'Array' | 'ReadonlyArray', + tsElementType: $FlowFixMe, +): Nullable { + 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, + > = { + type: 'ArrayTypeAnnotation', + elementType: wrapNullable(isElementTypeNullable, elementType), + }; + + return wrapNullable(nullable, finalTypeAnnotation); + } catch (ex) { + return wrapNullable(nullable, { + type: 'ArrayTypeAnnotation', + }); + } +} + function translateTypeAnnotation( hasteModuleName: string, /** @@ -101,71 +172,10 @@ 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, - > = { - type: 'ArrayTypeAnnotation', - elementType: wrapNullable(isElementTypeNullable, elementType), - }; - - return wrapNullable(nullable, finalTypeAnnotation); - } catch (ex) { - return wrapNullable(nullable, { - type: 'ArrayTypeAnnotation', - }); - } + return translateArrayTypeAnnotation( + typeAnnotation.type, + typeAnnotation.typeParameters.params[0] + ); } case 'Readonly': { assertGenericTypeAnnotationHasExactlyOneTypeParameter( From 0086a57b4132ec70871b32234177791fe0e987cf Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" <53799235+ZihanChen-MSFT@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:16:20 -0700 Subject: [PATCH 3/6] ... --- .../src/parsers/typescript/modules/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/index.js b/packages/react-native-codegen/src/parsers/typescript/modules/index.js index c0508433b8d90b..6b0c7660e1610b 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -64,6 +64,7 @@ function nullGuard(fn: () => T): ?T { function translateArrayTypeAnnotation( tsArrayType: 'Array' | 'ReadonlyArray', tsElementType: $FlowFixMe, + nullable: $FlowFixMe, ): Nullable { try { /** @@ -174,7 +175,8 @@ function translateTypeAnnotation( return translateArrayTypeAnnotation( typeAnnotation.type, - typeAnnotation.typeParameters.params[0] + typeAnnotation.typeParameters.params[0], + nullable, ); } case 'Readonly': { From f79cce51ded12664084f4a22eb7e8eb0a3a4a3f7 Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" <53799235+ZihanChen-MSFT@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:22:01 -0700 Subject: [PATCH 4/6] ... --- .../src/parsers/typescript/modules/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/index.js b/packages/react-native-codegen/src/parsers/typescript/modules/index.js index 6b0c7660e1610b..a5051906c2bbcc 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -62,6 +62,10 @@ function nullGuard(fn: () => T): ?T { } function translateArrayTypeAnnotation( + hasteModuleName: string, + types: TypeDeclarationMap, + aliasMap: {...NativeModuleAliasMap}, + cxxOnly: boolean, tsArrayType: 'Array' | 'ReadonlyArray', tsElementType: $FlowFixMe, nullable: $FlowFixMe, @@ -174,6 +178,10 @@ function translateTypeAnnotation( ); return translateArrayTypeAnnotation( + hasteModuleName, + types, + aliasMap, + cxxOnly, typeAnnotation.type, typeAnnotation.typeParameters.params[0], nullable, From da10c32e26af36a8920742ec19f62dc50d2d6a71 Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" <53799235+ZihanChen-MSFT@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:28:11 -0700 Subject: [PATCH 5/6] Recognize TypeScript array type --- .../src/parsers/typescript/modules/index.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/index.js b/packages/react-native-codegen/src/parsers/typescript/modules/index.js index a5051906c2bbcc..cf44eeda60ed93 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -152,6 +152,35 @@ 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': { From 7185c421b3450d76d39a0e96d9b76e3fde26382e Mon Sep 17 00:00:00 2001 From: "Zihan Chen (MSFT)" <53799235+ZihanChen-MSFT@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:31:09 -0700 Subject: [PATCH 6/6] prettier --- .../src/parsers/typescript/modules/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/index.js b/packages/react-native-codegen/src/parsers/typescript/modules/index.js index cf44eeda60ed93..d0e395e31ac007 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -164,7 +164,10 @@ function translateTypeAnnotation( ); } case 'TSTypeOperator': { - if (typeAnnotation.operator === 'readonly' && typeAnnotation.typeAnnotation.type === 'TSArrayType') { + if ( + typeAnnotation.operator === 'readonly' && + typeAnnotation.typeAnnotation.type === 'TSArrayType' + ) { return translateArrayTypeAnnotation( hasteModuleName, types,