diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js index 74dc51368ad802..00de83d9b4f706 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js @@ -225,18 +225,7 @@ describe('isObjectProperty', () => { expect(result).toEqual(true); }); - it("returns 'true' if 'property.type' is 'ObjectTypeIndexer'", () => { - const result = isObjectProperty( - { - type: 'ObjectTypeIndexer', - ...propertyStub, - }, - language, - ); - expect(result).toEqual(true); - }); - - it("returns 'false' if 'property.type' is not 'ObjectTypeProperty' or 'ObjectTypeIndexer'", () => { + it("returns 'false' if 'property.type' is not 'ObjectTypeProperty'", () => { const result = isObjectProperty( { type: 'notObjectTypeProperty', @@ -261,18 +250,7 @@ describe('isObjectProperty', () => { expect(result).toEqual(true); }); - it("returns 'true' if 'property.type' is 'TSIndexSignature'", () => { - const result = isObjectProperty( - { - type: 'TSIndexSignature', - ...propertyStub, - }, - language, - ); - expect(result).toEqual(true); - }); - - it("returns 'false' if 'property.type' is not 'TSPropertySignature' or 'TSIndexSignature'", () => { + it("returns 'false' if 'property.type' is not 'TSPropertySignature'", () => { const result = isObjectProperty( { type: 'notTSPropertySignature', @@ -295,7 +273,7 @@ describe('parseObjectProperty', () => { describe("when 'language' is 'Flow'", () => { const language: ParserType = 'Flow'; - it("throws an 'UnsupportedObjectPropertyTypeAnnotationParserError' error if 'property.type' is not 'ObjectTypeProperty' or 'ObjectTypeIndexer'.", () => { + it("throws an 'UnsupportedObjectPropertyTypeAnnotationParserError' error if 'property.type' is not 'ObjectTypeProperty'.", () => { const property = { type: 'notObjectTypeProperty', typeAnnotation: { @@ -329,7 +307,7 @@ describe('parseObjectProperty', () => { describe("when 'language' is 'TypeScript'", () => { const language: ParserType = 'TypeScript'; - it("throws an 'UnsupportedObjectPropertyTypeAnnotationParserError' error if 'property.type' is not 'TSPropertySignature' or 'TSIndexSignature'.", () => { + it("throws an 'UnsupportedObjectPropertyTypeAnnotationParserError' error if 'property.type' is not 'TSPropertySignature'.", () => { const property = { type: 'notTSPropertySignature', typeAnnotation: { @@ -358,40 +336,5 @@ describe('parseObjectProperty', () => { ), ).toThrow(expected); }); - - it("returns a 'NativeModuleBaseTypeAnnotation' object with 'typeAnnotation.type' equal to 'GenericObjectTypeAnnotation', if 'property.type' is 'TSIndexSignature'.", () => { - const property = { - type: 'TSIndexSignature', - typeAnnotation: { - type: 'TSIndexSignature', - typeAnnotation: 'TSIndexSignature', - }, - key: { - name: 'testKeyName', - }, - value: 'wrongValue', - name: 'wrongName', - parameters: [{name: 'testName'}], - }; - const result = parseObjectProperty( - property, - moduleName, - types, - aliasMap, - tryParse, - cxxOnly, - nullable, - typeScriptTranslateTypeAnnotation, - typeScriptParser, - ); - const expected = { - name: 'testName', - optional: false, - typeAnnotation: wrapNullable(nullable, { - type: 'GenericObjectTypeAnnotation', - }), - }; - expect(result).toEqual(expected); - }); }); }); diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js index 870814d34a5972..b982faaad04235 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js @@ -37,32 +37,6 @@ describe('FlowParser', () => { }); }); - describe('when propertyOrIndex is ObjectTypeIndexer', () => { - it('returns indexer name', () => { - const indexer = { - type: 'ObjectTypeIndexer', - id: { - name: 'indexerName', - }, - }; - - const expected = 'indexerName'; - - expect(parser.getKeyName(indexer, hasteModuleName)).toEqual(expected); - }); - - it('returns `key` if indexer has no name', () => { - const indexer = { - type: 'ObjectTypeIndexer', - id: {}, - }; - - const expected = 'key'; - - expect(parser.getKeyName(indexer, hasteModuleName)).toEqual(expected); - }); - }); - describe('when propertyOrIndex is not ObjectTypeProperty or ObjectTypeIndexer', () => { it('throw UnsupportedObjectPropertyTypeAnnotationParserError', () => { const indexer = { @@ -113,23 +87,6 @@ describe('TypeScriptParser', () => { }); }); - describe('when propertyOrIndex is TSIndexSignature', () => { - it('returns indexer name', () => { - const indexer = { - type: 'TSIndexSignature', - parameters: [ - { - name: 'indexerName', - }, - ], - }; - - const expected = 'indexerName'; - - expect(parser.getKeyName(indexer, hasteModuleName)).toEqual(expected); - }); - }); - describe('when propertyOrIndex is not TSPropertySignature or TSIndexSignature', () => { it('throw UnsupportedObjectPropertyTypeAnnotationParserError', () => { const indexer = { 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 904ca7d5d42640..df2687fc9a7a83 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 @@ -712,7 +712,8 @@ export interface Spec extends TurboModule { returnObjectArray(): Promise>; returnNullableNumber(): Promise; returnEmpty(): Promise; - returnIndex(): Promise<{ [string]: 'authorized' | 'denied' | 'undetermined' | true | false }>; + returnUnsupportedIndex(): Promise<{ [string]: 'authorized' | 'denied' | 'undetermined' | true | false }>; + returnSupportedIndex(): Promise<{ [string]: CustomObject }>; returnEnum() : Promise; returnObject() : Promise; } 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 6c0f6c6f4aa6d0..7e1eb7fd2b6a20 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 @@ -126,32 +126,14 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`] 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'b', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' }, 'params': [ { 'name': 'arg', 'optional': false, 'typeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'a', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' } } ] @@ -163,32 +145,14 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`] 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'key', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' }, 'params': [ { 'name': 'arg', 'optional': false, 'typeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'key', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' } } ] @@ -1810,23 +1774,25 @@ exports[`RN Codegen Flow Parser can generate fixture PROMISE_WITH_COMMONLY_USED_ } }, { - 'name': 'returnIndex', + 'name': 'returnUnsupportedIndex', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'PromiseTypeAnnotation' + }, + 'params': [] + } + }, + { + 'name': 'returnSupportedIndex', 'optional': false, 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { 'type': 'PromiseTypeAnnotation', 'elementType': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'key', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' } }, 'params': [] 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 120d6fa8443fd1..fae2c38d0a3b4b 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -166,6 +166,28 @@ function translateTypeAnnotation( } } case 'ObjectTypeAnnotation': { + // if there is any indexer, then it is a dictionary + if (typeAnnotation.indexers) { + const indexers = typeAnnotation.indexers.filter( + member => member.type === 'ObjectTypeIndexer', + ); + if (indexers.length > 0) { + // check the property type to prevent developers from using unsupported types + // the return value from `translateTypeAnnotation` is unused + const propertyType = indexers[0].value; + translateTypeAnnotation( + hasteModuleName, + propertyType, + types, + aliasMap, + tryParse, + cxxOnly, + ); + // no need to do further checking + return emitObject(nullable); + } + } + const objectTypeAnnotation = { type: 'ObjectTypeAnnotation', // $FlowFixMe[missing-type-arg] @@ -238,8 +260,9 @@ function translateTypeAnnotation( case 'MixedTypeAnnotation': { if (cxxOnly) { return emitMixed(nullable); + } else { + return emitObject(nullable); } - // Fallthrough } default: { throw new UnsupportedTypeAnnotationParserError( diff --git a/packages/react-native-codegen/src/parsers/flow/parser.js b/packages/react-native-codegen/src/parsers/flow/parser.js index b0fd878f9c2cb8..eee1b29cc82b64 100644 --- a/packages/react-native-codegen/src/parsers/flow/parser.js +++ b/packages/react-native-codegen/src/parsers/flow/parser.js @@ -21,21 +21,20 @@ const { class FlowParser implements Parser { typeParameterInstantiation: string = 'TypeParameterInstantiation'; - getKeyName(propertyOrIndex: $FlowFixMe, hasteModuleName: string): string { - switch (propertyOrIndex.type) { - case 'ObjectTypeProperty': - return propertyOrIndex.key.name; - case 'ObjectTypeIndexer': - // flow index name is optional - return propertyOrIndex.id?.name ?? 'key'; - default: - throw new UnsupportedObjectPropertyTypeAnnotationParserError( - hasteModuleName, - propertyOrIndex, - propertyOrIndex.type, - this.language(), - ); + isProperty(property: $FlowFixMe): boolean { + return property.type === 'ObjectTypeProperty'; + } + + getKeyName(property: $FlowFixMe, hasteModuleName: string): string { + if (!this.isProperty(property)) { + throw new UnsupportedObjectPropertyTypeAnnotationParserError( + hasteModuleName, + property, + property.type, + this.language(), + ); } + return property.key.name; } getMaybeEnumMemberType(maybeEnumDeclaration: $FlowFixMe): string { diff --git a/packages/react-native-codegen/src/parsers/parser.js b/packages/react-native-codegen/src/parsers/parser.js index fdb03cd32b8fb6..df2b7931ef9f54 100644 --- a/packages/react-native-codegen/src/parsers/parser.js +++ b/packages/react-native-codegen/src/parsers/parser.js @@ -24,13 +24,17 @@ export interface Parser { typeParameterInstantiation: string; /** - * Given a property or an index declaration, it returns the key name. - * @parameter propertyOrIndex: an object containing a property or an index declaration. + * Given a declaration, it returns true if it is a property + */ + isProperty(property: $FlowFixMe): boolean; + /** + * Given a property declaration, it returns the key name. + * @parameter property: an object containing a property declaration. * @parameter hasteModuleName: a string with the native module name. * @returns: the key name. - * @throws if propertyOrIndex does not contain a property or an index declaration. + * @throws if property does not contain a property declaration. */ - getKeyName(propertyOrIndex: $FlowFixMe, hasteModuleName: string): string; + getKeyName(property: $FlowFixMe, hasteModuleName: string): string; /** * Given a type declaration, it possibly returns the name of the Enum type. * @parameter maybeEnumDeclaration: an object possibly containing an Enum declaration. diff --git a/packages/react-native-codegen/src/parsers/parserMock.js b/packages/react-native-codegen/src/parsers/parserMock.js index 6f3d16eba978fe..2040c0c24ba7aa 100644 --- a/packages/react-native-codegen/src/parsers/parserMock.js +++ b/packages/react-native-codegen/src/parsers/parserMock.js @@ -21,21 +21,20 @@ const { export class MockedParser implements Parser { typeParameterInstantiation: string = 'TypeParameterInstantiation'; - getKeyName(propertyOrIndex: $FlowFixMe, hasteModuleName: string): string { - switch (propertyOrIndex.type) { - case 'ObjectTypeProperty': - return propertyOrIndex.key.name; - case 'ObjectTypeIndexer': - // flow index name is optional - return propertyOrIndex.id?.name ?? 'key'; - default: - throw new UnsupportedObjectPropertyTypeAnnotationParserError( - hasteModuleName, - propertyOrIndex, - propertyOrIndex.type, - this.language(), - ); + isProperty(property: $FlowFixMe): boolean { + return property.type === 'ObjectTypeProperty'; + } + + getKeyName(property: $FlowFixMe, hasteModuleName: string): string { + if (!this.isProperty(property)) { + throw new UnsupportedObjectPropertyTypeAnnotationParserError( + hasteModuleName, + property, + property.type, + this.language(), + ); } + return property.key.name; } getMaybeEnumMemberType(maybeEnumDeclaration: $FlowFixMe): string { diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index a7884085f91069..d1d6654823c66a 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -40,7 +40,6 @@ const { MoreThanOneTypeParameterGenericParserError, UnsupportedEnumDeclarationParserError, UnsupportedGenericParserError, - UnsupportedObjectPropertyTypeAnnotationParserError, UnnamedFunctionParamParserError, } = require('./errors'); @@ -116,15 +115,9 @@ function assertGenericTypeAnnotationHasExactlyOneTypeParameter( function isObjectProperty(property: $FlowFixMe, language: ParserType): boolean { switch (language) { case 'Flow': - return ( - property.type === 'ObjectTypeProperty' || - property.type === 'ObjectTypeIndexer' - ); + return property.type === 'ObjectTypeProperty'; case 'TypeScript': - return ( - property.type === 'TSPropertySignature' || - property.type === 'TSIndexSignature' - ); + return property.type === 'TSPropertySignature'; default: return false; } @@ -143,35 +136,13 @@ function parseObjectProperty( ): NamedShape> { const language = parser.language(); - if (!isObjectProperty(property, language)) { - throw new UnsupportedObjectPropertyTypeAnnotationParserError( - hasteModuleName, - property, - property.type, - language, - ); - } - - const {optional = false} = property; const name = parser.getKeyName(property, hasteModuleName); + const {optional = false} = property; const languageTypeAnnotation = language === 'TypeScript' ? property.typeAnnotation.typeAnnotation : property.value; - if ( - property.type === 'ObjectTypeIndexer' || - property.type === 'TSIndexSignature' - ) { - return { - name, - optional, - typeAnnotation: wrapNullable(nullable, { - type: 'GenericObjectTypeAnnotation', - }), //TODO: use `emitObject` for typeAnnotation - }; - } - const [propertyTypeAnnotation, isPropertyNullable] = unwrapNullable<$FlowFixMe>( translateTypeAnnotation( 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 3e337fdf909704..9d9dc39a4d411b 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 @@ -124,32 +124,14 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'b', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' }, 'params': [ { 'name': 'arg', 'optional': false, 'typeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'a', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' } } ] @@ -161,32 +143,14 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL 'typeAnnotation': { 'type': 'FunctionTypeAnnotation', 'returnTypeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'key', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' }, 'params': [ { 'name': 'arg', 'optional': false, 'typeAnnotation': { - 'type': 'ObjectTypeAnnotation', - 'properties': [ - { - 'name': 'key', - 'optional': false, - 'typeAnnotation': { - 'type': 'GenericObjectTypeAnnotation' - } - } - ] + 'type': 'GenericObjectTypeAnnotation' } } ] 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 5991c7c8e3b369..7c1750ad3354e5 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -184,6 +184,28 @@ function translateTypeAnnotation( } } case 'TSTypeLiteral': { + // if there is TSIndexSignature, then it is a dictionary + if (typeAnnotation.members) { + const indexSignatures = typeAnnotation.members.filter( + member => member.type === 'TSIndexSignature', + ); + if (indexSignatures.length > 0) { + // check the property type to prevent developers from using unsupported types + // the return value from `translateTypeAnnotation` is unused + const propertyType = indexSignatures[0].typeAnnotation; + translateTypeAnnotation( + hasteModuleName, + propertyType, + types, + aliasMap, + tryParse, + cxxOnly, + ); + // no need to do further checking + return emitObject(nullable); + } + } + const objectTypeAnnotation = { type: 'ObjectTypeAnnotation', // $FlowFixMe[missing-type-arg] diff --git a/packages/react-native-codegen/src/parsers/typescript/parser.js b/packages/react-native-codegen/src/parsers/typescript/parser.js index 12d2d5beb06b62..a6a22ac32832d7 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parser.js +++ b/packages/react-native-codegen/src/parsers/typescript/parser.js @@ -21,20 +21,20 @@ const { class TypeScriptParser implements Parser { typeParameterInstantiation: string = 'TSTypeParameterInstantiation'; - getKeyName(propertyOrIndex: $FlowFixMe, hasteModuleName: string): string { - switch (propertyOrIndex.type) { - case 'TSPropertySignature': - return propertyOrIndex.key.name; - case 'TSIndexSignature': - return propertyOrIndex.parameters[0].name; - default: - throw new UnsupportedObjectPropertyTypeAnnotationParserError( - hasteModuleName, - propertyOrIndex, - propertyOrIndex.type, - this.language(), - ); + isProperty(property: $FlowFixMe): boolean { + return property.type === 'TSPropertySignature'; + } + + getKeyName(property: $FlowFixMe, hasteModuleName: string): string { + if (!this.isProperty(property)) { + throw new UnsupportedObjectPropertyTypeAnnotationParserError( + hasteModuleName, + property, + property.type, + this.language(), + ); } + return property.key.name; } getMaybeEnumMemberType(maybeEnumDeclaration: $FlowFixMe): string {