From 414f1cfb3dcf975f3803c121763db266c42feac4 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 27 Oct 2022 23:53:09 -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: 682621a230710fb1f241d06fb75724a7c2eccad5 --- .../modules/__test_fixtures__/fixtures.js | 2 + .../module-parser-snapshot-test.js.snap | 80 +++++++++++++++++++ .../src/parsers/flow/modules/index.js | 17 ++-- .../src/parsers/parsers-commons.js | 30 ++++++- .../modules/__test_fixtures__/fixtures.js | 2 + ...script-module-parser-snapshot-test.js.snap | 80 +++++++++++++++++++ .../src/parsers/typescript/modules/index.js | 11 ++- 7 files changed, 213 insertions(+), 9 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..c3718298981715 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,86 @@ 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': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + } + } + ] + } + } + ] + } + }, + { + 'name': 'getAnotherMap', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + ] + } + } + ] + } + }, { '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 8cfd853d4fcbb2..21e72cf77ef580 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 { UnsupportedTypeAnnotationParserError, UnsupportedFunctionParamTypeAnnotationParserError, UnsupportedEnumDeclarationParserError, - UnsupportedUnionTypeAnnotationParserError, UnsupportedObjectPropertyTypeAnnotationParserError, IncorrectModuleRegistryCallArgumentTypeParserError, } = require('../../errors.js'); @@ -83,6 +82,7 @@ const { } = require('../../error-utils'); const {FlowParser} = require('../parser.js'); +import {getKeyName} from '../../parsers-commons'; const language = 'Flow'; const parser = new FlowParser(); @@ -288,11 +288,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, @@ -301,7 +307,8 @@ function translateTypeAnnotation( ); } - const {optional, key} = property; + const {optional = false} = property; + const name = getKeyName(property, hasteModuleName, language); const [propertyTypeAnnotation, isPropertyNullable] = unwrapNullable( @@ -329,7 +336,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..3526a8245b1818 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,86 @@ 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': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'a', + 'optional': false, + 'typeAnnotation': { + 'type': 'NullableTypeAnnotation', + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + } + } + ] + } + } + ] + } + }, + { + 'name': 'getAnotherMap', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + ] + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'key', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + } + ] + } + } + ] + } + }, { '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 9c6f6080faa54e..590c938e40a796 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -81,6 +81,7 @@ const { } = require('../../error-utils'); const {TypeScriptParser} = require('../parser'); +import {getKeyName} from '../../parsers-commons'; const language = 'TypeScript'; const parser = new TypeScriptParser(); @@ -305,7 +306,10 @@ function translateTypeAnnotation( .map>>( property => { return tryParse(() => { - if (property.type !== 'TSPropertySignature') { + if ( + property.type !== 'TSPropertySignature' && + property.type !== 'TSIndexSignature' + ) { throw new UnsupportedObjectPropertyTypeAnnotationParserError( hasteModuleName, property, @@ -314,7 +318,8 @@ function translateTypeAnnotation( ); } - const {optional = false, key} = property; + const {optional = false} = property; + const name = getKeyName(property, hasteModuleName, language); const [propertyTypeAnnotation, isPropertyNullable] = unwrapNullable( @@ -342,7 +347,7 @@ function translateTypeAnnotation( ); } else { return { - name: key.name, + name, optional, typeAnnotation: wrapNullable( isPropertyNullable,