diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 6884ca30710b..fc96c5a372ef 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -299,6 +299,7 @@ export type NativeModuleTypeAliasTypeAnnotation = $ReadOnly<{ export type NativeModulePromiseTypeAnnotation = $ReadOnly<{ type: 'PromiseTypeAnnotation', + elementType?: Nullable, }>; export type UnionTypeAnnotationMemberType = diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js index 082af17c1df3..b55fa616a5f8 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-primitives-test.js @@ -338,6 +338,31 @@ describe('typeAliasResolution', () => { describe('emitPromise', () => { const moduleName = 'testModuleName'; + function emitPromiseForUnitTest( + typeAnnotation: $FlowFixMe, + nullable: boolean, + ): $FlowFixMe { + return emitPromise( + moduleName, + typeAnnotation, + parser, + nullable, + // mock translateTypeAnnotation function + /* types: TypeDeclarationMap */ + {}, + /* aliasMap: {...NativeModuleAliasMap} */ + {}, + /* tryParse: ParserErrorCapturer */ + function (_: () => T) { + return null; + }, + /* cxxOnly: boolean */ + false, + /* the translateTypeAnnotation function */ + (_, elementType) => elementType, + ); + } + describe("when typeAnnotation doesn't have exactly one typeParameter", () => { const typeAnnotation = { typeParameters: { @@ -350,9 +375,7 @@ describe('emitPromise', () => { }; it('throws an IncorrectlyParameterizedGenericParserError error', () => { const nullable = false; - expect(() => - emitPromise(moduleName, typeAnnotation, parser, nullable), - ).toThrow(); + expect(() => emitPromiseForUnitTest(typeAnnotation, nullable)).toThrow(); }); }); @@ -370,16 +393,12 @@ describe('emitPromise', () => { describe('when nullable is true', () => { const nullable = true; it('returns nullable type annotation', () => { - const result = emitPromise( - moduleName, - typeAnnotation, - parser, - nullable, - ); + const result = emitPromiseForUnitTest(typeAnnotation, nullable); const expected = { type: 'NullableTypeAnnotation', typeAnnotation: { type: 'PromiseTypeAnnotation', + elementType: 1, }, }; @@ -389,14 +408,10 @@ describe('emitPromise', () => { describe('when nullable is false', () => { const nullable = false; it('returns non nullable type annotation', () => { - const result = emitPromise( - moduleName, - typeAnnotation, - parser, - nullable, - ); + const result = emitPromiseForUnitTest(typeAnnotation, nullable); const expected = { type: 'PromiseTypeAnnotation', + elementType: 1, }; expect(result).toEqual(expected); diff --git a/packages/react-native-codegen/src/parsers/consistency/__tests__/checkModuleSnaps-test.js b/packages/react-native-codegen/src/parsers/consistency/__tests__/checkModuleSnaps-test.js index 1e392b8dde6b..e14a6523156a 100644 --- a/packages/react-native-codegen/src/parsers/consistency/__tests__/checkModuleSnaps-test.js +++ b/packages/react-native-codegen/src/parsers/consistency/__tests__/checkModuleSnaps-test.js @@ -14,7 +14,7 @@ const {compareSnaps, compareTsArraySnaps} = require('../compareSnaps.js'); const flowFixtures = require('../../flow/modules/__test_fixtures__/fixtures.js'); const flowSnaps = require('../../../../src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap'); -const flowExtraCases = []; +const flowExtraCases = ['PROMISE_WITH_COMMONLY_USED_TYPES']; const tsFixtures = require('../../typescript/modules/__test_fixtures__/fixtures.js'); const tsSnaps = require('../../../../src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap'); const tsExtraCases = [ 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 3b42068a003e..904ca7d5d426 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 @@ -682,6 +682,44 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModuleCxx'); `; +const PROMISE_WITH_COMMONLY_USED_TYPES = ` +/** + * 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 * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export type Season = 'Spring' | 'Summer' | 'Autumn' | 'Winter'; + +export type CustomObject = {| + field1: Array, + field2: boolean, + field3: string, + type: 'A_String_Literal', +|}; + +export interface Spec extends TurboModule { + returnStringArray(): Promise>; + returnObjectArray(): Promise>; + returnNullableNumber(): Promise; + returnEmpty(): Promise; + returnIndex(): Promise<{ [string]: 'authorized' | 'denied' | 'undetermined' | true | false }>; + returnEnum() : Promise; + returnObject() : Promise; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); +`; + module.exports = { NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY, NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE, @@ -705,4 +743,5 @@ module.exports = { ANDROID_ONLY_NATIVE_MODULE, IOS_ONLY_NATIVE_MODULE, CXX_ONLY_NATIVE_MODULE, + PROMISE_WITH_COMMONLY_USED_TYPES, }; 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 32222a962a22..c8c143c5cfda 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 @@ -1506,7 +1506,20 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PROMISE 'modules': { 'NativeSampleTurboModule': { 'type': 'NativeModule', - 'aliases': {}, + 'aliases': { + 'SomeObj': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + ] + } + }, 'spec': { 'properties': [ { @@ -1515,7 +1528,10 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PROMISE 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'PromiseTypeAnnotation' + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'StringTypeAnnotation' + } }, 'params': [] } @@ -1526,7 +1542,10 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PROMISE 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'PromiseTypeAnnotation' + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'StringTypeAnnotation' + } }, 'params': [] } @@ -1537,7 +1556,11 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PROMISE 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'PromiseTypeAnnotation' + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'SomeObj' + } }, 'params': [] } @@ -1727,3 +1750,168 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_UNSAFE_O } }" `; + +exports[`RN Codegen Flow Parser can generate fixture PROMISE_WITH_COMMONLY_USED_TYPES 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliases': { + 'CustomObject': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'field1', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'GenericObjectTypeAnnotation' + } + } + }, + { + 'name': 'field2', + 'optional': false, + 'typeAnnotation': { + 'type': 'BooleanTypeAnnotation' + } + }, + { + 'name': 'field3', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + }, + { + 'name': 'type', + 'optional': false, + 'typeAnnotation': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'StringTypeAnnotation' + } + } + ] + } + }, + 'spec': { + 'properties': [ + { + 'name': 'returnStringArray', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'StringTypeAnnotation' + } + } + }, + 'params': [] + } + }, + { + 'name': 'returnObjectArray', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'GenericObjectTypeAnnotation' + } + } + }, + 'params': [] + } + }, + { + 'name': 'returnNullableNumber', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation' + }, + 'params': [] + } + }, + { + 'name': 'returnEmpty', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation' + }, + 'params': [] + } + }, + { + 'name': 'returnIndex', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + } + }, + 'params': [] + } + }, + { + 'name': 'returnEnum', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'UnionTypeAnnotation', + 'memberType': 'StringTypeAnnotation' + } + }, + 'params': [] + } + }, + { + 'name': 'returnObject', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'CustomObject' + } + }, + 'params': [] + } + } + ] + }, + 'moduleNames': [ + 'SampleTurboModule' + ] + } + } +}" +`; 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 90f3568c406f..9c0b44d9396a 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -150,7 +150,17 @@ function translateTypeAnnotation( return emitRootTag(nullable); } case 'Promise': { - return emitPromise(hasteModuleName, typeAnnotation, parser, nullable); + return emitPromise( + hasteModuleName, + typeAnnotation, + parser, + nullable, + types, + aliasMap, + tryParse, + cxxOnly, + translateTypeAnnotation, + ); } case 'Array': case '$ReadOnlyArray': { @@ -285,6 +295,13 @@ function translateTypeAnnotation( parser, ); } + case 'StringLiteralTypeAnnotation': { + // 'a' is a special case for 'a' | 'b' but the type name is different + return wrapNullable(nullable, { + type: 'UnionTypeAnnotation', + memberType: 'StringTypeAnnotation', + }); + } case 'MixedTypeAnnotation': { if (cxxOnly) { return emitMixedTypeAnnotation(nullable); diff --git a/packages/react-native-codegen/src/parsers/parsers-primitives.js b/packages/react-native-codegen/src/parsers/parsers-primitives.js index 5a3f4b04314f..c6404dbfabb5 100644 --- a/packages/react-native-codegen/src/parsers/parsers-primitives.js +++ b/packages/react-native-codegen/src/parsers/parsers-primitives.js @@ -186,6 +186,11 @@ function emitPromise( typeAnnotation: $FlowFixMe, parser: Parser, nullable: boolean, + types: TypeDeclarationMap, + aliasMap: {...NativeModuleAliasMap}, + tryParse: ParserErrorCapturer, + cxxOnly: boolean, + translateTypeAnnotation: $FlowFixMe, ): Nullable { assertGenericTypeAnnotationHasExactlyOneTypeParameter( hasteModuleName, @@ -193,9 +198,33 @@ function emitPromise( parser, ); - return wrapNullable(nullable, { - type: 'PromiseTypeAnnotation', - }); + const elementType = typeAnnotation.typeParameters.params[0]; + if ( + elementType.type === 'ExistsTypeAnnotation' || + elementType.type === 'EmptyTypeAnnotation' + ) { + return wrapNullable(nullable, { + type: 'PromiseTypeAnnotation', + }); + } else { + try { + return wrapNullable(nullable, { + type: 'PromiseTypeAnnotation', + elementType: translateTypeAnnotation( + hasteModuleName, + typeAnnotation.typeParameters.params[0], + types, + aliasMap, + tryParse, + cxxOnly, + ), + }); + } catch { + return wrapNullable(nullable, { + type: 'PromiseTypeAnnotation', + }); + } + } } function emitObject( 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 c787dcbcbb0d..d49327021ef2 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 @@ -1713,7 +1713,20 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_PR 'modules': { 'NativeSampleTurboModule': { 'type': 'NativeModule', - 'aliases': {}, + 'aliases': { + 'SomeObj': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + ] + } + }, 'spec': { 'properties': [ { @@ -1722,7 +1735,10 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_PR 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'PromiseTypeAnnotation' + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'StringTypeAnnotation' + } }, 'params': [] } @@ -1733,7 +1749,10 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_PR 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'PromiseTypeAnnotation' + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'StringTypeAnnotation' + } }, 'params': [] } @@ -1744,7 +1763,11 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_PR 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'PromiseTypeAnnotation' + 'type': 'PromiseTypeAnnotation', + 'elementType': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'SomeObj' + } }, 'params': [] } 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 c184e715479a..058b32094846 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -185,7 +185,17 @@ function translateTypeAnnotation( return emitRootTag(nullable); } case 'Promise': { - return emitPromise(hasteModuleName, typeAnnotation, parser, nullable); + return emitPromise( + hasteModuleName, + typeAnnotation, + parser, + nullable, + types, + aliasMap, + tryParse, + cxxOnly, + translateTypeAnnotation, + ); } case 'Array': case 'ReadonlyArray': {