diff --git a/packages/react-native-codegen/src/CodegenSchema.d.ts b/packages/react-native-codegen/src/CodegenSchema.d.ts index fc13854cb0d..e3487768b66 100644 --- a/packages/react-native-codegen/src/CodegenSchema.d.ts +++ b/packages/react-native-codegen/src/CodegenSchema.d.ts @@ -60,7 +60,7 @@ export interface MixedTypeAnnotation { export interface EventEmitterTypeAnnotation { readonly type: 'EventEmitterTypeAnnotation'; - readonly typeAnnotation: NativeModuleBaseTypeAnnotation; + readonly typeAnnotation: NativeModuleEventEmitterTypeAnnotation; } export interface FunctionTypeAnnotation { @@ -360,6 +360,23 @@ export interface NativeModuleMixedTypeAnnotation { readonly type: 'MixedTypeAnnotation'; } +export type NativeModuleEventEmitterBaseTypeAnnotation = + | NativeModuleBooleanTypeAnnotation + | NativeModuleDoubleTypeAnnotation + | NativeModuleFloatTypeAnnotation + | NativeModuleInt32TypeAnnotation + | NativeModuleNumberTypeAnnotation + | NativeModuleStringTypeAnnotation + | NativeModuleTypeAliasTypeAnnotation + | VoidTypeAnnotation; + +export type NativeModuleEventEmitterTypeAnnotation = + | NativeModuleEventEmitterBaseTypeAnnotation + | { + readonly type: 'ArrayTypeAnnotation'; + readonly elementType: NativeModuleEventEmitterBaseTypeAnnotation | {type: string}; + }; + export type NativeModuleBaseTypeAnnotation = | NativeModuleStringTypeAnnotation | NativeModuleNumberTypeAnnotation @@ -387,7 +404,8 @@ export type NativeModuleReturnTypeAnnotation = export type NativeModuleTypeAnnotation = | NativeModuleBaseTypeAnnotation | NativeModuleParamOnlyTypeAnnotation - | NativeModuleReturnOnlyTypeAnnotation; + | NativeModuleReturnOnlyTypeAnnotation + | NativeModuleEventEmitterTypeAnnotation; type NativeModuleParamOnlyTypeAnnotation = NativeModuleFunctionTypeAnnotation; diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index d53c3942159..3d13164dc6e 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -63,7 +63,7 @@ export type MixedTypeAnnotation = $ReadOnly<{ type EventEmitterTypeAnnotation = $ReadOnly<{ type: 'EventEmitterTypeAnnotation', - typeAnnotation: NativeModuleBaseTypeAnnotation, + typeAnnotation: NativeModuleEventEmitterTypeAnnotation | $FlowFixMe, }>; type FunctionTypeAnnotation<+P, +R> = $ReadOnly<{ @@ -366,6 +366,23 @@ export type NativeModuleMixedTypeAnnotation = $ReadOnly<{ type: 'MixedTypeAnnotation', }>; +type NativeModuleEventEmitterBaseTypeAnnotation = + | NativeModuleBooleanTypeAnnotation + | NativeModuleDoubleTypeAnnotation + | NativeModuleFloatTypeAnnotation + | NativeModuleInt32TypeAnnotation + | NativeModuleNumberTypeAnnotation + | NativeModuleStringTypeAnnotation + | NativeModuleTypeAliasTypeAnnotation + | VoidTypeAnnotation; + +export type NativeModuleEventEmitterTypeAnnotation = + | NativeModuleEventEmitterBaseTypeAnnotation + | { + type: 'ArrayTypeAnnotation', + elementType: NativeModuleEventEmitterBaseTypeAnnotation | {type: string}, + }; + export type NativeModuleBaseTypeAnnotation = | NativeModuleStringTypeAnnotation | NativeModuleNumberTypeAnnotation @@ -393,7 +410,8 @@ export type NativeModuleReturnTypeAnnotation = export type NativeModuleTypeAnnotation = | NativeModuleBaseTypeAnnotation | NativeModuleParamOnlyTypeAnnotation - | NativeModuleReturnOnlyTypeAnnotation; + | NativeModuleReturnOnlyTypeAnnotation + | NativeModuleEventEmitterTypeAnnotation; type NativeModuleParamOnlyTypeAnnotation = NativeModuleFunctionTypeAnnotation; diff --git a/packages/react-native-codegen/src/parsers/errors.js b/packages/react-native-codegen/src/parsers/errors.js index dde9cf45ac5..101138a7e10 100644 --- a/packages/react-native-codegen/src/parsers/errors.js +++ b/packages/react-native-codegen/src/parsers/errors.js @@ -107,7 +107,7 @@ class UnsupportedModuleEventEmitterPropertyParserError extends ParserError { message += `'${propertyValue}' must non nullable.`; } else if (untyped) { message += `'${propertyValue}' must have a concrete or void eventType.`; - } else if (cxxOnly) { + } else if (!cxxOnly) { message += `'${propertyValue}' is only supported in C++ Turbo Modules.`; } super(nativeModuleName, propertyValue, message); 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 534a2ceedb7..4b1efc38eed 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 @@ -620,6 +620,42 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const NATIVE_MODULE_WITH_EVENT_EMITTERS = ` +/** + * 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. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import type {EventEmitter} from '../CodegenTypes'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export type ObjectStruct = { + a: number, + b: string, + c?: ?string, +}; + +export interface Spec extends TurboModule { + +onEvent1: EventEmitter; + +onEvent2: EventEmitter; + +onEvent3: EventEmitter; + +onEvent4: EventEmitter; + +onEvent5: EventEmitter; + +onEvent6: EventEmitter; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModuleCxx'); + +`; + const ANDROID_ONLY_NATIVE_MODULE = ` /** * Copyright (c) Meta Platforms, Inc. and affiliates. @@ -813,6 +849,7 @@ module.exports = { NATIVE_MODULE_WITH_BASIC_PARAM_TYPES, NATIVE_MODULE_WITH_CALLBACK, NATIVE_MODULE_WITH_UNION, + NATIVE_MODULE_WITH_EVENT_EMITTERS, EMPTY_NATIVE_MODULE, ANDROID_ONLY_NATIVE_MODULE, IOS_ONLY_NATIVE_MODULE, 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 e7e7dc4a559..17684eeea46 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 @@ -1424,6 +1424,123 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ }" `; +exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_EVENT_EMITTERS 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliasMap': { + 'ObjectStruct': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + }, + { + 'name': 'b', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + }, + { + 'name': 'c', + 'optional': true, + 'typeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + } + ] + } + }, + 'enumMap': {}, + 'spec': { + 'eventEmitters': [ + { + 'name': 'onEvent1', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'VoidTypeAnnotation' + } + } + }, + { + 'name': 'onEvent2', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + }, + { + 'name': 'onEvent3', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + } + }, + { + 'name': 'onEvent4', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'BooleanTypeAnnotation' + } + } + }, + { + 'name': 'onEvent5', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'ObjectStruct' + } + } + }, + { + 'name': 'onEvent6', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'ObjectStruct' + } + } + } + } + ], + 'methods': [] + }, + 'moduleName': 'SampleTurboModuleCxx', + 'excludedPlatforms': [ + 'iOS', + 'android' + ] + } + } +}" +`; + exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_FLOAT_AND_INT32 1`] = ` "{ 'modules': { 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 d08345353a3..adfea71f5b1 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -40,6 +40,7 @@ const { emitPromise, emitRootTag, emitUnion, + translateArrayTypeAnnotation, typeAliasResolution, typeEnumResolution, } = require('../../parsers-primitives'); @@ -62,6 +63,20 @@ function translateTypeAnnotation( resolveTypeAnnotationFN(flowTypeAnnotation, types, parser); switch (typeAnnotation.type) { + case 'ArrayTypeAnnotation': { + return translateArrayTypeAnnotation( + hasteModuleName, + types, + aliasMap, + enumMap, + cxxOnly, + 'Array', + typeAnnotation.elementType, + nullable, + translateTypeAnnotation, + parser, + ); + } case 'GenericTypeAnnotation': { switch (parser.getTypeAnnotationName(typeAnnotation)) { case 'RootTag': { diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 443fc7dff71..f012943eade 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -489,15 +489,22 @@ function buildEventEmitterSchema( translateTypeAnnotation: $FlowFixMe, parser: Parser, ): NativeModuleEventEmitterShape { - let {key, value} = property; - const eventemitterName: string = key.name; + const {key} = property; + const value = + parser.language() === 'TypeScript' + ? property.typeAnnotation.typeAnnotation + : property.value; + const eventemitterName: string = key.name; const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN(); const [typeAnnotation, typeAnnotationNullable] = unwrapNullable(value); const typeAnnotationUntyped = value.typeParameters.params.length === 1 && - value.typeParameters.params[0].type === 'ObjectTypeAnnotation' && - value.typeParameters.params[0].properties.length === 0; + parser.language() === 'TypeScript' + ? value.typeParameters.params[0].type === 'TSTypeLiteral' && + value.typeParameters.params[0].members.length === 0 + : value.typeParameters.params[0].type === 'ObjectTypeAnnotation' && + value.typeParameters.params[0].properties.length === 0; throwIfEventEmitterTypeIsUnsupported( hasteModuleName, @@ -520,12 +527,24 @@ function buildEventEmitterSchema( parser, eventTypeResolutionStatus.nullable, ); + + const eventTypeAnnotation = translateTypeAnnotation( + hasteModuleName, + typeAnnotation.typeParameters.params[0], + types, + aliasMap, + enumMap, + tryParse, + cxxOnly, + parser, + ); + return { name: eventemitterName, optional: false, typeAnnotation: { type: 'EventEmitterTypeAnnotation', - typeAnnotation: {type: eventTypeResolutionStatus.typeAnnotation.type}, + typeAnnotation: eventTypeAnnotation, }, }; } @@ -775,7 +794,6 @@ const buildModuleSchema = ( parser, ) : {}; - const properties: $ReadOnlyArray<$FlowFixMe> = language === 'Flow' ? moduleSpec.body.properties : moduleSpec.body.body; @@ -798,8 +816,12 @@ const buildModuleSchema = ( }>(property => { const enumMap: {...NativeModuleEnumMap} = {}; const isEventEmitter = - property?.value?.type === 'GenericTypeAnnotation' && - property?.value?.id?.name === 'EventEmitter'; + language === 'TypeScript' + ? property?.type === 'TSPropertySignature' && + property?.typeAnnotation?.typeAnnotation?.typeName?.name === + 'EventEmitter' + : property?.value?.type === 'GenericTypeAnnotation' && + property?.value?.id?.name === 'EventEmitter'; return tryParse(() => ({ aliasMap, enumMap, 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 453e1891440..56a3a87a074 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 @@ -723,6 +723,38 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const NATIVE_MODULE_WITH_EVENT_EMITTERS = ` +/** + * 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 ObjectStruct = { + a: number; + b: string; + c?: string | null; +}; + +export interface Spec extends TurboModule { + readonly onEvent1: EventEmitter; + readonly onEvent2: EventEmitter; + readonly onEvent3: EventEmitter; + readonly onEvent4: EventEmitter; + readonly onEvent5: EventEmitter; + readonly onEvent6: EventEmitter; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModuleCxx'); + +`; + const ANDROID_ONLY_NATIVE_MODULE = ` /** * Copyright (c) Meta Platforms, Inc. and affiliates. @@ -875,6 +907,7 @@ module.exports = { NATIVE_MODULE_WITH_BASIC_PARAM_TYPES, NATIVE_MODULE_WITH_CALLBACK, NATIVE_MODULE_WITH_UNION, + NATIVE_MODULE_WITH_EVENT_EMITTERS, EMPTY_NATIVE_MODULE, ANDROID_ONLY_NATIVE_MODULE, IOS_ONLY_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 4ae381e47a7..6eeda94587b 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 @@ -1624,6 +1624,123 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_CO }" `; +exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_EVENT_EMITTERS 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliasMap': { + 'ObjectStruct': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + }, + { + 'name': 'b', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + }, + { + 'name': 'c', + 'optional': true, + 'typeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + } + ] + } + }, + 'enumMap': {}, + 'spec': { + 'eventEmitters': [ + { + 'name': 'onEvent1', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'VoidTypeAnnotation' + } + } + }, + { + 'name': 'onEvent2', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + }, + { + 'name': 'onEvent3', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + } + }, + { + 'name': 'onEvent4', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'BooleanTypeAnnotation' + } + } + }, + { + 'name': 'onEvent5', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'ObjectStruct' + } + } + }, + { + 'name': 'onEvent6', + 'optional': false, + 'typeAnnotation': { + 'type': 'EventEmitterTypeAnnotation', + 'typeAnnotation': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'ObjectStruct' + } + } + } + } + ], + 'methods': [] + }, + 'moduleName': 'SampleTurboModuleCxx', + 'excludedPlatforms': [ + 'iOS', + 'android' + ] + } + } +}" +`; + exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_FLOAT_AND_INT32 1`] = ` "{ 'modules': { diff --git a/packages/react-native-codegen/src/parsers/typescript/parser.js b/packages/react-native-codegen/src/parsers/typescript/parser.js index 7262b60301b..04f8c1d69e6 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parser.js +++ b/packages/react-native-codegen/src/parsers/typescript/parser.js @@ -337,6 +337,8 @@ class TypeScriptParser implements Parser { convertKeywordToTypeAnnotation(keyword: string): string { switch (keyword) { + case 'TSArrayType': + return 'ArrayTypeAnnotation'; case 'TSBooleanKeyword': return 'BooleanTypeAnnotation'; case 'TSNumberKeyword': @@ -345,6 +347,8 @@ class TypeScriptParser implements Parser { return 'VoidTypeAnnotation'; case 'TSStringKeyword': return 'StringTypeAnnotation'; + case 'TSTypeLiteral': + return 'ObjectTypeAnnotation'; case 'TSUnknownKeyword': return 'MixedTypeAnnotation'; }