From 87c356d56c73c3289da3d5911288909720b11994 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Sun, 30 Oct 2022 05:48:57 -0700 Subject: [PATCH] Add Map / indexed object support for TypeScript parser (#35098) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/35098 Changelog: [General][Fixed] [react-native-codegen] react-native-codegen : Add Map / indexed object support for TypeScript parser In flow we can expose Maps via the following syntax in TM specs ` +getMap: (arg: {[key: string]: ?number}) => {[key: string]: ?number}; ` In TypeScript writing the same spec: ` readonly getMap: (arg: { [key: string]: number | null; }) => { [key: string]: number | null; }; ` leads to an exception the TypeScript code-gen parser ```UnsupportedObjectPropertyTypeAnnotationParserError: Module NativeTurboModuleCxx: 'ObjectTypeAnnotation' cannot contain 'TSIndexSignature'. at react-native-github/packages/react-native-codegen/src/parsers/typescript/modules/index.js:309:23``` ``` This change fixes the TypeScript parser Reviewed By: cipolleschi Differential Revision: D40753368 fbshipit-source-id: 0eef8ecb63d1ed049fde1e75cc6f2ec627f1f232 --- .../modules/__test_fixtures__/fixtures.js | 2 + .../module-parser-snapshot-test.js.snap | 74 +++++++++++++++++++ .../src/parsers/flow/modules/index.js | 25 +++++-- .../src/parsers/parsers-commons.js | 30 +++++++- .../modules/__test_fixtures__/fixtures.js | 2 + ...script-module-parser-snapshot-test.js.snap | 74 +++++++++++++++++++ .../src/parsers/typescript/modules/index.js | 19 ++++- 7 files changed, 215 insertions(+), 11 deletions(-) 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 50e4e18aa4c2c6..e8668947ce28c1 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 @@ -644,6 +644,8 @@ export interface Spec extends TurboModule { +getCallback: () => () => void; +getMixed: (arg: mixed) => mixed; +getEnums: (quality: Quality, resolution?: Resolution, floppy: Floppy, stringOptions: StringOptions) => string; + +getMap: (arg: {[a: string]: ?number}) => {[b: string]: ?number}; + +getAnotherMap: (arg: {[string]: string}) => {[string]: string}; +getUnion: (chooseInt: ChooseInt, chooseFloat: ChooseFloat, chooseObject: ChooseObject, chooseString: ChooseString) => ChooseObject; } 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 e73868dc872a3c..8f308ace82dca7 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 @@ -122,6 +122,80 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`] ] } }, + { + 'name': 'getMap', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'b', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + } + } + ] + } + }, + { + 'name': 'getAnotherMap', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + } + } + ] + } + }, { 'name': 'getUnion', 'optional': false, 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 90bbfeda7e3de2..761931268a68d6 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -62,7 +62,6 @@ const { UnsupportedGenericParserError, UnsupportedTypeAnnotationParserError, UnsupportedEnumDeclarationParserError, - UnsupportedUnionTypeAnnotationParserError, UnsupportedObjectPropertyTypeAnnotationParserError, IncorrectModuleRegistryCallArgumentTypeParserError, } = require('../../errors.js'); @@ -84,6 +83,7 @@ const { } = require('../../error-utils'); const {FlowParser} = require('../parser.js'); +const {getKeyName} = require('../../parsers-commons'); const language = 'Flow'; const parser = new FlowParser(); @@ -287,11 +287,17 @@ function translateTypeAnnotation( const objectTypeAnnotation = { type: 'ObjectTypeAnnotation', // $FlowFixMe[missing-type-arg] - properties: (typeAnnotation.properties: Array<$FlowFixMe>) + properties: ([ + ...typeAnnotation.properties, + ...typeAnnotation.indexers, + ]: Array<$FlowFixMe>) .map>>( property => { return tryParse(() => { - if (property.type !== 'ObjectTypeProperty') { + if ( + property.type !== 'ObjectTypeProperty' && + property.type !== 'ObjectTypeIndexer' + ) { throw new UnsupportedObjectPropertyTypeAnnotationParserError( hasteModuleName, property, @@ -300,8 +306,15 @@ function translateTypeAnnotation( ); } - const {optional, key} = property; - + const {optional = false} = property; + const name = getKeyName(property, hasteModuleName, language); + if (property.type === 'ObjectTypeIndexer') { + return { + name, + optional, + typeAnnotation: emitObject(nullable), + }; + } const [propertyTypeAnnotation, isPropertyNullable] = unwrapNullable( translateTypeAnnotation( @@ -328,7 +341,7 @@ function translateTypeAnnotation( ); } else { return { - name: key.name, + name, optional, typeAnnotation: wrapNullable( isPropertyNullable, diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index eda3b3ef2990d3..1528399b066608 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -24,7 +24,9 @@ const { UnsupportedUnionTypeAnnotationParserError, } = require('./errors'); import type {ParserType} from './errors'; - +const { + UnsupportedObjectPropertyTypeAnnotationParserError, +} = require('./errors'); const invariant = require('invariant'); function wrapModuleSchema( @@ -155,6 +157,31 @@ function emitUnionTypeAnnotation( }); } +function getKeyName( + propertyOrIndex: $FlowFixMe, + hasteModuleName: string, + language: ParserType, +): string { + switch (propertyOrIndex.type) { + case 'ObjectTypeProperty': + case 'TSPropertySignature': + return propertyOrIndex.key.name; + case 'ObjectTypeIndexer': + // flow index name is optional + return propertyOrIndex.id?.name ?? 'key'; + case 'TSIndexSignature': + // TypeScript index name is mandatory + return propertyOrIndex.parameters[0].name; + default: + throw new UnsupportedObjectPropertyTypeAnnotationParserError( + hasteModuleName, + propertyOrIndex, + propertyOrIndex.type, + language, + ); + } +} + module.exports = { wrapModuleSchema, unwrapNullable, @@ -162,4 +189,5 @@ module.exports = { assertGenericTypeAnnotationHasExactlyOneTypeParameter, emitMixedTypeAnnotation, emitUnionTypeAnnotation, + getKeyName, }; 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 02a2ae68b98a14..83483075d5ba49 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 @@ -661,6 +661,8 @@ export interface Spec extends TurboModule { readonly getCallback: () => () => void; readonly getMixed: (arg: unknown) => unknown; readonly getEnums: (quality: Quality, resolution?: Resolution, floppy: Floppy, stringOptions: StringOptions) => string; + readonly getMap: (arg: {[a: string]: number | null;}) => {[b: string]: number | null;}; + readonly getAnotherMap: (arg: {[key: string]: string}) => {[key: string]: string}; readonly getUnion: (chooseInt: ChooseInt, chooseFloat: ChooseFloat, chooseObject: ChooseObject, chooseString: ChooseString) => ChooseObject; } 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 6b4ff88450528a..185a836a066752 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 @@ -120,6 +120,80 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL ] } }, + { + 'name': 'getMap', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'b', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + } + } + ] + } + }, + { + 'name': 'getAnotherMap', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'GenericObjectTypeAnnotation' + } + } + ] + } + } + ] + } + }, { 'name': 'getUnion', 'optional': false, 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 5971cc84661026..f080a5190a5d55 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -84,6 +84,7 @@ const { } = require('../../error-utils'); const {TypeScriptParser} = require('../parser'); +const {getKeyName} = require('../../parsers-commons'); const language = 'TypeScript'; const parser = new TypeScriptParser(); @@ -306,7 +307,10 @@ function translateTypeAnnotation( .map>>( property => { return tryParse(() => { - if (property.type !== 'TSPropertySignature') { + if ( + property.type !== 'TSPropertySignature' && + property.type !== 'TSIndexSignature' + ) { throw new UnsupportedObjectPropertyTypeAnnotationParserError( hasteModuleName, property, @@ -315,8 +319,15 @@ function translateTypeAnnotation( ); } - const {optional = false, key} = property; - + const {optional = false} = property; + const name = getKeyName(property, hasteModuleName, language); + if (property.type === 'TSIndexSignature') { + return { + name, + optional, + typeAnnotation: emitObject(nullable), + }; + } const [propertyTypeAnnotation, isPropertyNullable] = unwrapNullable( translateTypeAnnotation( @@ -343,7 +354,7 @@ function translateTypeAnnotation( ); } else { return { - name: key.name, + name, optional, typeAnnotation: wrapNullable( isPropertyNullable,