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 e14a6523156a73..e90d3d22d30c0e 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 @@ -22,6 +22,7 @@ const tsExtraCases = [ 'NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE', 'NATIVE_MODULE_WITH_BASIC_ARRAY2', 'NATIVE_MODULE_WITH_COMPLEX_ARRAY2', + 'NATIVE_MODULE_WITH_NESTED_INTERFACES', ]; const ignoredCases = []; 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 6e71b23714936d..56b22b61841504 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 @@ -195,6 +195,50 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const NATIVE_MODULE_WITH_NESTED_INTERFACES = ` +/** + * 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 '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +interface Bar { + z: number +}; + +interface Base1 { + bar1: Bar, +} + +interface Base2 { + bar2: Bar, +} + +interface Base3 extends Base2 { + bar3: Bar, +} + +interface Foo extends Base1, Base3 { + bar4: Bar, +}; + +export interface Spec extends TurboModule { + // Exported methods. + foo1: (x: Foo) => Foo; + foo2: (x: Foo) => void; +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + const NATIVE_MODULE_WITH_FLOAT_AND_INT32 = ` /** * Copyright (c) Meta Platforms, Inc. and affiliates. @@ -704,6 +748,7 @@ module.exports = { NATIVE_MODULE_WITH_FLOAT_AND_INT32, NATIVE_MODULE_WITH_ALIASES, NATIVE_MODULE_WITH_NESTED_ALIASES, + NATIVE_MODULE_WITH_NESTED_INTERFACES, NATIVE_MODULE_WITH_PROMISE, NATIVE_MODULE_WITH_COMPLEX_OBJECTS, NATIVE_MODULE_WITH_COMPLEX_OBJECTS_WITH_NULLABLE_KEY, 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 9d9dc39a4d411b..d5ed3ec8c416a0 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 @@ -1503,6 +1503,113 @@ exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_NE }" `; +exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_NESTED_INTERFACES 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliases': { + 'Bar': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'z', + 'optional': false, + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + } + ] + }, + 'Foo': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'bar1', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'Bar' + } + }, + { + 'name': 'bar2', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'Bar' + } + }, + { + 'name': 'bar3', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'Bar' + } + }, + { + 'name': 'bar4', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'Bar' + } + } + ] + } + }, + 'spec': { + 'properties': [ + { + 'name': 'foo1', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'Foo' + }, + 'params': [ + { + 'name': 'x', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'Foo' + } + } + ] + } + }, + { + 'name': 'foo2', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'VoidTypeAnnotation' + }, + 'params': [ + { + 'name': 'x', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'Foo' + } + } + ] + } + } + ] + }, + 'moduleName': 'SampleTurboModule' + } + } +}" +`; + exports[`RN Codegen TypeScript Parser can generate fixture NATIVE_MODULE_WITH_NULLABLE_PARAM 1`] = ` "{ 'modules': { 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 f3a182d71b8ea4..b1df8711a58b92 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -22,6 +22,7 @@ import type { import type {Parser} from '../../parser'; import type {ParserErrorCapturer, TypeDeclarationMap} from '../../utils'; +const {flattenProperties} = require('../components/componentsUtils'); const {visit, isModuleRegistryCall, verifyPlatforms} = require('../../utils'); const {resolveTypeAnnotation, getTypes} = require('../utils'); @@ -182,6 +183,41 @@ function translateTypeAnnotation( } } } + case 'TSInterfaceDeclaration': { + const objectTypeAnnotation = { + type: 'ObjectTypeAnnotation', + // $FlowFixMe[missing-type-arg] + properties: (flattenProperties( + [typeAnnotation], + types, + ): $ReadOnlyArray<$FlowFixMe>) + .map>>( + property => { + return tryParse(() => { + return parseObjectProperty( + property, + hasteModuleName, + types, + aliasMap, + tryParse, + cxxOnly, + nullable, + translateTypeAnnotation, + parser, + ); + }); + }, + ) + .filter(Boolean), + }; + + return typeAliasResolution( + typeAliasResolutionStatus, + objectTypeAnnotation, + aliasMap, + nullable, + ); + } case 'TSTypeLiteral': { // if there is TSIndexSignature, then it is a dictionary if (typeAnnotation.members) { diff --git a/packages/react-native-codegen/src/parsers/typescript/utils.js b/packages/react-native-codegen/src/parsers/typescript/utils.js index 83e2f8bd2def75..725b74968ae6cc 100644 --- a/packages/react-native-codegen/src/parsers/typescript/utils.js +++ b/packages/react-native-codegen/src/parsers/typescript/utils.js @@ -90,12 +90,18 @@ function resolveTypeAnnotation( break; } - invariant( - resolvedTypeAnnotation.type === 'TSTypeAliasDeclaration', - `GenericTypeAnnotation '${node.typeName.name}' must resolve to a TSTypeAliasDeclaration. Instead, it resolved to a '${resolvedTypeAnnotation.type}'`, - ); - - node = resolvedTypeAnnotation.typeAnnotation; + switch (resolvedTypeAnnotation.type) { + case 'TSTypeAliasDeclaration': + node = resolvedTypeAnnotation.typeAnnotation; + break; + case 'TSInterfaceDeclaration': + node = resolvedTypeAnnotation; + break; + default: + throw new Error( + `GenericTypeAnnotation '${node.typeName.name}' must resolve to a TSTypeAliasDeclaration or a TSInterfaceDeclaration. Instead, it resolved to a '${resolvedTypeAnnotation.type}'`, + ); + } } else { break; }