Enable EventEmitter parsing for TypeScript TM Specs (#45118)

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

## Changelog:

[General] [Fixed] - Enable EventEmitter parsing for TypeScript TM Specs

Reviewed By: rshest

Differential Revision: D58929364

fbshipit-source-id: 0f95aee2f387edf0a148b368d71b0325c805f724
This commit is contained in:
Christoph Purrer
2024-06-24 11:36:49 -07:00
committed by Facebook GitHub Bot
parent af04eb773c
commit 6daccf75da
10 changed files with 394 additions and 13 deletions
+20 -2
View File
@@ -60,7 +60,7 @@ export interface MixedTypeAnnotation {
export interface EventEmitterTypeAnnotation {
readonly type: 'EventEmitterTypeAnnotation';
readonly typeAnnotation: NativeModuleBaseTypeAnnotation;
readonly typeAnnotation: NativeModuleEventEmitterTypeAnnotation;
}
export interface FunctionTypeAnnotation<P, R> {
@@ -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;
+20 -2
View File
@@ -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;
+1 -1
View File
@@ -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);
@@ -620,6 +620,42 @@ export default TurboModuleRegistry.getEnforcing<Spec>('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<void>;
+onEvent2: EventEmitter<string>;
+onEvent3: EventEmitter<number>;
+onEvent4: EventEmitter<boolean>;
+onEvent5: EventEmitter<ObjectStruct>;
+onEvent6: EventEmitter<ObjectStruct[]>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('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,
@@ -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': {
@@ -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': {
@@ -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,
@@ -723,6 +723,38 @@ export default TurboModuleRegistry.getEnforcing<Spec>('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<void>;
readonly onEvent2: EventEmitter<string>;
readonly onEvent3: EventEmitter<number>;
readonly onEvent4: EventEmitter<boolean>;
readonly onEvent5: EventEmitter<ObjectStruct>;
readonly onEvent6: EventEmitter<ObjectStruct[]>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('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,
@@ -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': {
@@ -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';
}