From 355feafff6129129533c939e707fadb5fc747c08 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Wed, 24 Aug 2022 14:47:39 -0700 Subject: [PATCH 01/27] react-native-code-gen Add Union Type support for C++ TurboModules Summary: There are cases where we want to pass a union type into a TurboModule method which is handy to restrict certain arguments to a restricted set of values, e.g. restricting quality to ```'low'```, ```high``` or resolution to ```720```, ```1080```. - We are not generating an ```union``` type in C++ but rather just cast type safe to the corresponding member type. - We don't support mixed primitive union types at this time, e.g. ```export type ChooseSomething = 'One' | 'Two' | 3 | false;``` does not work. - We can support mixed object union types such as ... ```export type ChooseObject = {} | {low: string};``` - which need special logic in the C++ TM to correctly parse the resulting jsi::Object This is for C++ only, Java and ObjC are not supported atm. Changelog: [General][Added] - react-native-code-gen Add Union Type support for C++ TurboModules Reviewed By: javache Differential Revision: D38919688 fbshipit-source-id: 0fd37545b32b4f2059a8babda62dab4a85de37a9 --- .../react-native-codegen/src/CodegenSchema.js | 11 +++ .../generators/modules/GenerateModuleCpp.js | 13 ++++ .../src/generators/modules/GenerateModuleH.js | 11 +++ .../modules/GenerateModuleJavaSpec.js | 6 +- .../modules/GenerateModuleJniCpp.js | 6 +- .../GenerateModuleObjCpp/StructCollector.js | 2 + .../GenerateModuleObjCpp/serializeMethod.js | 4 +- .../modules/__test_fixtures__/fixtures.js | 45 ++++++++++++ .../GenerateModuleCpp-test.js.snap | 4 ++ .../GenerateModuleH-test.js.snap | 9 +++ .../modules/__test_fixtures__/fixtures.js | 6 ++ .../module-parser-snapshot-test.js.snap | 45 ++++++++++++ .../src/parsers/flow/modules/errors.js | 25 +++++++ .../src/parsers/flow/modules/index.js | 28 ++++++++ .../modules/__test_fixtures__/fixtures.js | 6 ++ ...script-module-parser-snapshot-test.js.snap | 69 +++++++++++++++---- .../src/parsers/typescript/modules/errors.js | 25 +++++++ .../src/parsers/typescript/modules/index.js | 28 ++++++++ 18 files changed, 323 insertions(+), 20 deletions(-) diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 8776caaa9232a9..8ffc00612548b4 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -295,6 +295,16 @@ export type NativeModulePromiseTypeAnnotation = $ReadOnly<{ type: 'PromiseTypeAnnotation', }>; +export type UnionTypeAnnotationMemberType = + | 'NumberTypeAnnotation' + | 'ObjectTypeAnnotation' + | 'StringTypeAnnotation'; + +export type NativeModuleUnionTypeAnnotation = $ReadOnly<{ + type: 'UnionTypeAnnotation', + memberType: UnionTypeAnnotationMemberType, +}>; + export type NativeModuleMixedTypeAnnotation = $ReadOnly<{ type: 'MixedTypeAnnotation', }>; @@ -311,6 +321,7 @@ export type NativeModuleBaseTypeAnnotation = | NativeModuleTypeAliasTypeAnnotation | NativeModuleArrayTypeAnnotation> | NativeModuleObjectTypeAnnotation + | NativeModuleUnionTypeAnnotation | NativeModuleMixedTypeAnnotation; export type NativeModuleParamTypeAnnotation = diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js index 046e86b1c65e0a..d49ed9253154e2 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js @@ -167,6 +167,19 @@ function serializeArg( return wrap(val => `${val}.asObject(rt).asFunction(rt)`); case 'GenericObjectTypeAnnotation': return wrap(val => `${val}.asObject(rt)`); + case 'UnionTypeAnnotation': + switch (typeAnnotation.memberType) { + case 'NumberTypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'ObjectTypeAnnotation': + return wrap(val => `${val}.asObject(rt)`); + case 'StringTypeAnnotation': + return wrap(val => `${val}.asString(rt)`); + default: + throw new Error( + `Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`, + ); + } case 'ObjectTypeAnnotation': return wrap(val => `${val}.asObject(rt)`); case 'MixedTypeAnnotation': diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 1f518d3f47e0f9..6e5f97a8551a39 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -148,6 +148,17 @@ function translatePrimitiveJSTypeToCpp( return wrap('bool'); case 'GenericObjectTypeAnnotation': return wrap('jsi::Object'); + case 'UnionTypeAnnotation': + switch (typeAnnotation.memberType) { + case 'NumberTypeAnnotation': + return wrap('double'); + case 'ObjectTypeAnnotation': + return wrap('jsi::Object'); + case 'StringTypeAnnotation': + return wrap('jsi::String'); + default: + throw new Error(createErrorMessage(realTypeAnnotation.type)); + } case 'ObjectTypeAnnotation': return wrap('jsi::Object'); case 'ArrayTypeAnnotation': diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 538371a7c49520..aa833afbba30c3 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -156,7 +156,7 @@ function translateFunctionParamToJavaType( imports.add('com.facebook.react.bridge.Callback'); return 'Callback'; default: - (realTypeAnnotation.type: 'MixedTypeAnnotation'); + (realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error(createErrorMessage(realTypeAnnotation.type)); } } @@ -220,7 +220,7 @@ function translateFunctionReturnTypeToJavaType( imports.add('com.facebook.react.bridge.WritableArray'); return wrapIntoNullableIfNeeded('WritableArray'); default: - (realTypeAnnotation.type: 'MixedTypeAnnotation'); + (realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error(createErrorMessage(realTypeAnnotation.type)); } } @@ -272,7 +272,7 @@ function getFalsyReturnStatementFromReturnType( case 'ArrayTypeAnnotation': return 'return null;'; default: - (realTypeAnnotation.type: 'MixedTypeAnnotation'); + (realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error(createErrorMessage(realTypeAnnotation.type)); } } diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js index 4adb37c943c56d..bca66c4f9925a4 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js @@ -171,7 +171,7 @@ function translateReturnTypeToKind( case 'ArrayTypeAnnotation': return 'ArrayKind'; default: - (realTypeAnnotation.type: 'MixedTypeAnnotation'); + (realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error( `Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`, ); @@ -226,7 +226,7 @@ function translateParamTypeToJniType( case 'FunctionTypeAnnotation': return 'Lcom/facebook/react/bridge/Callback;'; default: - (realTypeAnnotation.type: 'MixedTypeAnnotation'); + (realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error( `Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`, ); @@ -278,7 +278,7 @@ function translateReturnTypeToJniType( case 'ArrayTypeAnnotation': return 'Lcom/facebook/react/bridge/WritableArray;'; default: - (realTypeAnnotation.type: 'MixedTypeAnnotation'); + (realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error( `Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`, ); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js index e942ff3c978255..82e8d5bc245d18 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js @@ -114,6 +114,8 @@ class StructCollector { } case 'MixedTypeAnnotation': throw new Error('Mixed types are unsupported in structs'); + case 'UnionTypeAnnotation': + throw new Error('Union types are unsupported in structs'); default: { return wrapNullable(nullable, typeAnnotation); } diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js index a74367dcb87197..d7f33cecb7afcd 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -338,7 +338,7 @@ function getReturnObjCType( case 'GenericObjectTypeAnnotation': return wrapIntoNullableIfNeeded('NSDictionary *'); default: - (typeAnnotation.type: 'MixedTypeAnnotation'); + (typeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error( `Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`, ); @@ -378,7 +378,7 @@ function getReturnJSType( case 'GenericObjectTypeAnnotation': return 'ObjectKind'; default: - (typeAnnotation.type: 'MixedTypeAnnotation'); + (typeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation'); throw new Error( `Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`, ); diff --git a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js index 16f6bd517ba44d..590d3e939de5ca 100644 --- a/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js @@ -1567,6 +1567,51 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = { ], }, }, + { + name: 'getUnion', + optional: false, + typeAnnotation: { + type: 'FunctionTypeAnnotation', + returnTypeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'ObjectTypeAnnotation', + }, + params: [ + { + name: 'chooseInt', + optional: false, + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'NumberTypeAnnotation', + }, + }, + { + name: 'chooseFloat', + optional: false, + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'NumberTypeAnnotation', + }, + }, + { + name: 'chooseObject', + optional: false, + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'ObjectTypeAnnotation', + }, + }, + { + name: 'chooseString', + optional: false, + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'StringTypeAnnotation', + }, + }, + ], + }, + }, ], }, moduleNames: ['SampleTurboModuleCxx'], diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap index 93a57fc91bcb50..741eb73ee239ed 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleCpp-test.js.snap @@ -115,11 +115,15 @@ static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getNullableNu auto result = static_cast(&turboModule)->getNullableNumberFromNullableAlias(rt, args[0].isNull() || args[0].isUndefined() ? std::nullopt : std::make_optional(args[0].asObject(rt))); return result ? jsi::Value(std::move(*result)) : jsi::Value::null(); } +static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getUnion(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { + return static_cast(&turboModule)->getUnion(rt, args[0].asNumber(), args[1].asNumber(), args[2].asObject(rt), args[3].asString(rt)); +} NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared_ptr jsInvoker) : TurboModule(\\"SampleTurboModuleCxx\\", jsInvoker) { methodMap_[\\"getMixed\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getMixed}; methodMap_[\\"getNullableNumberFromNullableAlias\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getNullableNumberFromNullableAlias}; + methodMap_[\\"getUnion\\"] = MethodMetadata {4, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getUnion}; } diff --git a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap index 7fce7ea931084a..8cf7d8ee7f5315 100644 --- a/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap +++ b/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleH-test.js.snap @@ -209,6 +209,7 @@ protected: public: virtual jsi::Value getMixed(jsi::Runtime &rt, jsi::Value arg) = 0; virtual std::optional getNullableNumberFromNullableAlias(jsi::Runtime &rt, std::optional a) = 0; + virtual jsi::Object getUnion(jsi::Runtime &rt, double chooseInt, double chooseFloat, jsi::Object chooseObject, jsi::String chooseString) = 0; }; @@ -246,6 +247,14 @@ private: return bridging::callFromJs>( rt, &T::getNullableNumberFromNullableAlias, jsInvoker_, instance_, std::move(a)); } + jsi::Object getUnion(jsi::Runtime &rt, double chooseInt, double chooseFloat, jsi::Object chooseObject, jsi::String chooseString) override { + static_assert( + bridging::getParameterCount(&T::getUnion) == 5, + \\"Expected getUnion(...) to have 5 parameters\\"); + + return bridging::callFromJs( + rt, &T::getUnion, jsInvoker_, instance_, std::move(chooseInt), std::move(chooseFloat), std::move(chooseObject), std::move(chooseString)); + } private: T *instance_; diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js index cbbc156c29f97c..f9f84ee20ac15b 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js @@ -593,9 +593,15 @@ const CXX_ONLY_NATIVE_MODULE = ` import type {TurboModule} from '../RCTExport'; import * as TurboModuleRegistry from '../TurboModuleRegistry'; +export type ChooseInt = 1 | 2 | 3; +export type ChooseFloat = 1.44 | 2.88 | 5.76; +export type ChooseObject = {} | {low: string}; +export type ChooseString = 'One' | 'Two' | 'Three'; + export interface Spec extends TurboModule { +getCallback: () => () => void; +getMixed: (arg: mixed) => mixed; + +getUnion: (chooseInt: ChooseInt, chooseFloat: ChooseFloat, chooseObject: ChooseObject, chooseString: ChooseString) => ChooseObject; } export default TurboModuleRegistry.getEnforcing('SampleTurboModuleCxx'); diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap index aef18a0ef6c783..abe2c393988318 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap @@ -77,6 +77,51 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`] } ] } + }, + { + 'name': 'getUnion', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'ObjectTypeAnnotation' + }, + 'params': [ + { + 'name': 'chooseInt', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'NumberTypeAnnotation' + } + }, + { + 'name': 'chooseFloat', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'NumberTypeAnnotation' + } + }, + { + 'name': 'chooseObject', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'ObjectTypeAnnotation' + } + }, + { + 'name': 'chooseString', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'StringTypeAnnotation' + } + } + ] + } } ] }, diff --git a/packages/react-native-codegen/src/parsers/flow/modules/errors.js b/packages/react-native-codegen/src/parsers/flow/modules/errors.js index c8bb4253885e66..03bc6d63be7b08 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/errors.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/errors.js @@ -227,6 +227,30 @@ class UnsupportedFunctionReturnTypeAnnotationParserError extends ParserError { } } +/** + * Union parsing errors + */ + +class UnsupportedUnionTypeAnnotationParserError extends ParserError { + constructor( + hasteModuleName: string, + arrayElementTypeAST: $FlowFixMe, + types: string[], + ) { + super( + hasteModuleName, + arrayElementTypeAST, + `Union members must be of the same type, but multiple types were found ${types.join( + ', ', + )}'.`, + ); + } +} + +/** + * Module parsing errors + */ + class UnusedModuleFlowInterfaceParserError extends ParserError { constructor(hasteModuleName: string, flowInterface: $FlowFixMe) { super( @@ -323,6 +347,7 @@ module.exports = { UnsupportedFlowTypeAnnotationParserError, UnsupportedFunctionParamTypeAnnotationParserError, UnsupportedFunctionReturnTypeAnnotationParserError, + UnsupportedUnionTypeAnnotationParserError, UnsupportedModulePropertyParserError, UnsupportedObjectPropertyTypeAnnotationParserError, UnsupportedObjectPropertyValueTypeAnnotationParserError, diff --git a/packages/react-native-codegen/src/parsers/flow/modules/index.js b/packages/react-native-codegen/src/parsers/flow/modules/index.js index 38ce80db4e746c..528813922cf87f 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -44,6 +44,7 @@ const { UnsupportedFlowTypeAnnotationParserError, UnsupportedFunctionParamTypeAnnotationParserError, UnsupportedFunctionReturnTypeAnnotationParserError, + UnsupportedUnionTypeAnnotationParserError, UnsupportedModulePropertyParserError, UnsupportedObjectPropertyTypeAnnotationParserError, UnsupportedObjectPropertyValueTypeAnnotationParserError, @@ -367,6 +368,33 @@ function translateTypeAnnotation( ), ); } + case 'UnionTypeAnnotation': { + if (cxxOnly) { + // Remap literal names + const unionTypes = typeAnnotation.types + .map( + item => + item.type + .replace('NumberLiteralTypeAnnotation', 'NumberTypeAnnotation') + .replace('StringLiteralTypeAnnotation', 'StringTypeAnnotation'), + // ObjectAnnotation is already 'correct' + ) + .filter((value, index, self) => self.indexOf(value) === index); + // Only support unionTypes of the same kind + if (unionTypes.length > 1) { + throw new UnsupportedUnionTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + unionTypes, + ); + } + return wrapNullable(nullable, { + type: 'UnionTypeAnnotation', + memberType: unionTypes[0], + }); + } + // Fallthrough + } case 'MixedTypeAnnotation': { if (cxxOnly) { return wrapNullable(nullable, { 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 39956142c70e5e..f776e26ae3ca62 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 @@ -608,9 +608,15 @@ const CXX_ONLY_NATIVE_MODULE = ` import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; +export type ChooseInt = 1 | 2 | 3; +export type ChooseFloat = 1.44 | 2.88 | 5.76; +export type ChooseObject = {} | {low: string}; +export type ChooseString = 'One' | 'Two' | 'Three'; + export interface Spec extends TurboModule { readonly getCallback: () => () => void; readonly getMixed: (arg: unknown) => unknown; + readonly getUnion: (chooseInt: ChooseInt, chooseFloat: ChooseFloat, chooseObject: ChooseObject, chooseString: ChooseString) => ChooseObject; } export default TurboModuleRegistry.getEnforcing( 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 20420a83361108..e56dfc7871c8ad 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 @@ -75,6 +75,51 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL } ] } + }, + { + 'name': 'getUnion', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'ObjectTypeAnnotation' + }, + 'params': [ + { + 'name': 'chooseInt', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'NumberTypeAnnotation' + } + }, + { + 'name': 'chooseFloat', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'NumberTypeAnnotation' + } + }, + { + 'name': 'chooseObject', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'ObjectTypeAnnotation' + } + }, + { + 'name': 'chooseString', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'StringTypeAnnotation' + } + } + ] + } } ] }, @@ -369,7 +414,7 @@ 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`] = ` +exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE 1`] = ` "{ 'modules': { 'NativeSampleTurboModule': { @@ -383,20 +428,14 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_AR 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'ArrayTypeAnnotation', - 'elementType': { - 'type': 'StringTypeAnnotation' - } + 'type': 'ArrayTypeAnnotation' }, 'params': [ { 'name': 'arg', 'optional': false, 'typeAnnotation': { - 'type': 'ArrayTypeAnnotation', - 'elementType': { - 'type': 'StringTypeAnnotation' - } + 'type': 'ArrayTypeAnnotation' } } ] @@ -412,7 +451,7 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_AR }" `; -exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE 1`] = ` +exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_ARRAY2_WITH_ALIAS 1`] = ` "{ 'modules': { 'NativeSampleTurboModule': { @@ -426,14 +465,20 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_AR 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'ArrayTypeAnnotation' + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'StringTypeAnnotation' + } }, 'params': [ { 'name': 'arg', 'optional': false, 'typeAnnotation': { - 'type': 'ArrayTypeAnnotation' + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'StringTypeAnnotation' + } } } ] diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/errors.js b/packages/react-native-codegen/src/parsers/typescript/modules/errors.js index 357135d787578f..76064b5f0566af 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/errors.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/errors.js @@ -223,6 +223,30 @@ class UnsupportedFunctionReturnTypeAnnotationParserError extends ParserError { } } +/** + * Union parsing errors + */ + +class UnsupportedTypeScriptUnionTypeAnnotationParserError extends ParserError { + constructor( + hasteModuleName: string, + arrayElementTypeAST: $FlowFixMe, + types: string[], + ) { + super( + hasteModuleName, + arrayElementTypeAST, + `Union members must be of the same type, but multiple types were found ${types.join( + ', ', + )}'.`, + ); + } +} + +/** + * Module parsing errors + */ + class UnusedModuleTypeScriptInterfaceParserError extends ParserError { constructor(hasteModuleName: string, flowInterface: $FlowFixMe) { super( @@ -319,6 +343,7 @@ module.exports = { UnsupportedTypeScriptTypeAnnotationParserError, UnsupportedFunctionParamTypeAnnotationParserError, UnsupportedFunctionReturnTypeAnnotationParserError, + UnsupportedTypeScriptUnionTypeAnnotationParserError, UnsupportedModulePropertyParserError, UnsupportedObjectPropertyTypeAnnotationParserError, UnsupportedObjectPropertyValueTypeAnnotationParserError, 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 d0e395e31ac007..5db27cc5f9559d 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -44,6 +44,7 @@ const { UnsupportedTypeScriptTypeAnnotationParserError, UnsupportedFunctionParamTypeAnnotationParserError, UnsupportedFunctionReturnTypeAnnotationParserError, + UnsupportedTypeScriptUnionTypeAnnotationParserError, UnsupportedModulePropertyParserError, UnsupportedObjectPropertyTypeAnnotationParserError, UnsupportedObjectPropertyValueTypeAnnotationParserError, @@ -419,6 +420,33 @@ function translateTypeAnnotation( ), ); } + case 'TSUnionType': { + if (cxxOnly) { + // Remap literal names + const unionTypes = typeAnnotation.types + .map(item => + item.literal + ? item.literal.type + .replace('NumericLiteral', 'NumberTypeAnnotation') + .replace('StringLiteral', 'StringTypeAnnotation') + : 'ObjectTypeAnnotation', + ) + .filter((value, index, self) => self.indexOf(value) === index); + // Only support unionTypes of the same kind + if (unionTypes.length > 1) { + throw new UnsupportedTypeScriptUnionTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + unionTypes, + ); + } + return wrapNullable(nullable, { + type: 'UnionTypeAnnotation', + memberType: unionTypes[0], + }); + } + // Fallthrough + } case 'TSUnknownKeyword': { if (cxxOnly) { return wrapNullable(nullable, { From 6f60c5f3cd5f1d2f1daeb6b0c118f3fc4eb3a73c Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Wed, 24 Aug 2022 15:18:45 -0700 Subject: [PATCH 02/27] Add Flow type for ActivityIndicator Summary: Changelog: [Internal] - Add a type definition for ActivityIndicator Reviewed By: NickGerleman Differential Revision: D38850509 fbshipit-source-id: c3ca50be8fbcec0f0f43b036f8768f4462fa4991 --- .../ActivityIndicator.flow.js | 57 +++++++++++++++++++ .../ActivityIndicator/ActivityIndicator.js | 8 +-- 2 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 Libraries/Components/ActivityIndicator/ActivityIndicator.flow.js diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.flow.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.flow.js new file mode 100644 index 00000000000000..712a5b69410215 --- /dev/null +++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.flow.js @@ -0,0 +1,57 @@ +/** + * 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 + * @flow + */ + +'use strict'; +import * as React from 'react'; +import {type ColorValue} from '../../StyleSheet/StyleSheet'; +import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; +import type {ViewProps} from '../View/ViewPropTypes'; + +type IndicatorSize = number | 'small' | 'large'; + +type IOSProps = $ReadOnly<{| + /** + Whether the indicator should hide when not animating. + + @platform ios + */ + hidesWhenStopped?: ?boolean, +|}>; + +type Props = $ReadOnly<{| + ...ViewProps, + ...IOSProps, + + /** + Whether to show the indicator (`true`) or hide it (`false`). + */ + animating?: ?boolean, + + /** + The foreground color of the spinner. + + @default {@platform android} `null` (system accent default color) + @default {@platform ios} '#999999' + */ + color?: ?ColorValue, + + /** + Size of the indicator. + + @type enum(`'small'`, `'large'`) + @type {@platform android} number + */ + size?: ?IndicatorSize, +|}>; + +export type ActivityIndicator = React.AbstractComponent< + Props, + HostComponent, +>; diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.js index eb8b733e15a2cd..1c4fbd67f5d60a 100644 --- a/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -14,8 +14,8 @@ import * as React from 'react'; import Platform from '../../Utilities/Platform'; import StyleSheet, {type ColorValue} from '../../StyleSheet/StyleSheet'; import View from '../View/View'; -import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; import type {ViewProps} from '../View/ViewPropTypes'; +import type {ActivityIndicator as ActivityIndicatorType} from './ActivityIndicator.flow'; const PlatformActivityIndicator = Platform.OS === 'android' @@ -183,10 +183,8 @@ const ActivityIndicator = ( ``` */ -const ActivityIndicatorWithRef: React.AbstractComponent< - Props, - HostComponent, -> = React.forwardRef(ActivityIndicator); +const ActivityIndicatorWithRef: ActivityIndicatorType = + React.forwardRef(ActivityIndicator); ActivityIndicatorWithRef.displayName = 'ActivityIndicator'; const styles = StyleSheet.create({ From 163171ccab6937785f4f3c85e011bd14540bebf5 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Wed, 24 Aug 2022 15:18:45 -0700 Subject: [PATCH 03/27] Button Summary: Changelog: [Internal] - Add a type definition for Button Reviewed By: NickGerleman Differential Revision: D38850510 fbshipit-source-id: ffe137d01478d4a641afb85380a27522a058e91f --- Libraries/Components/Button.flow.js | 261 ++++++++++++++++++++++++++++ Libraries/Components/Button.js | 3 +- 2 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 Libraries/Components/Button.flow.js diff --git a/Libraries/Components/Button.flow.js b/Libraries/Components/Button.flow.js new file mode 100644 index 00000000000000..614c36af83f1e8 --- /dev/null +++ b/Libraries/Components/Button.flow.js @@ -0,0 +1,261 @@ +/** + * 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 + * @flow + * @generate-docs + */ + +'use strict'; + +import * as React from 'react'; +import {type ColorValue} from '../StyleSheet/StyleSheet'; + +import type { + AccessibilityState, + AccessibilityActionEvent, + AccessibilityActionInfo, +} from './View/ViewAccessibility'; +import type {PressEvent} from '../Types/CoreEventTypes'; + +type ButtonProps = $ReadOnly<{| + /** + Text to display inside the button. On Android the given title will be + converted to the uppercased form. + */ + title: string, + + /** + Handler to be called when the user taps the button. The first function + argument is an event in form of [PressEvent](pressevent). + */ + onPress: (event?: PressEvent) => mixed, + + /** + If `true`, doesn't play system sound on touch. + + @platform android + + @default false + */ + touchSoundDisabled?: ?boolean, + + /** + Color of the text (iOS), or background color of the button (Android). + + @default {@platform android} '#2196F3' + @default {@platform ios} '#007AFF' + */ + color?: ?ColorValue, + + /** + TV preferred focus. + + @platform tv + + @default false + */ + hasTVPreferredFocus?: ?boolean, + + /** + Designates the next view to receive focus when the user navigates down. See + the [Android documentation][android:nextFocusDown]. + + [android:nextFocusDown]: + https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusDown + + @platform android, tv + */ + nextFocusDown?: ?number, + + /** + Designates the next view to receive focus when the user navigates forward. + See the [Android documentation][android:nextFocusForward]. + + [android:nextFocusForward]: + https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusForward + + @platform android, tv + */ + nextFocusForward?: ?number, + + /** + Designates the next view to receive focus when the user navigates left. See + the [Android documentation][android:nextFocusLeft]. + + [android:nextFocusLeft]: + https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusLeft + + @platform android, tv + */ + nextFocusLeft?: ?number, + + /** + Designates the next view to receive focus when the user navigates right. See + the [Android documentation][android:nextFocusRight]. + + [android:nextFocusRight]: + https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusRight + + @platform android, tv + */ + nextFocusRight?: ?number, + + /** + Designates the next view to receive focus when the user navigates up. See + the [Android documentation][android:nextFocusUp]. + + [android:nextFocusUp]: + https://developer.android.com/reference/android/view/View.html#attr_android:nextFocusUp + + @platform android, tv + */ + nextFocusUp?: ?number, + + /** + Text to display for blindness accessibility features. + */ + accessibilityLabel?: ?string, + + /** + If `true`, disable all interactions for this component. + + @default false + */ + disabled?: ?boolean, + + /** + Used to locate this view in end-to-end tests. + */ + testID?: ?string, + + /** + * Accessibility props. + */ + accessible?: ?boolean, + accessibilityActions?: ?$ReadOnlyArray, + onAccessibilityAction?: ?(event: AccessibilityActionEvent) => mixed, + accessibilityState?: ?AccessibilityState, + + /** + * [Android] Controlling if a view fires accessibility events and if it is reported to accessibility services. + */ + importantForAccessibility?: ?('auto' | 'yes' | 'no' | 'no-hide-descendants'), + accessibilityHint?: ?string, + accessibilityLanguage?: ?Stringish, +|}>; + +/** + A basic button component that should render nicely on any platform. Supports a + minimal level of customization. + + If this button doesn't look right for your app, you can build your own button + using [TouchableOpacity](touchableopacity) or + [TouchableWithoutFeedback](touchablewithoutfeedback). For inspiration, look at + the [source code for this button component][button:source]. Or, take a look at + the [wide variety of button components built by the community] + [button:examples]. + + [button:source]: + https://github.com/facebook/react-native/blob/HEAD/Libraries/Components/Button.js + + [button:examples]: + https://js.coach/?menu%5Bcollections%5D=React%20Native&page=1&query=button + + ```jsx +