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 35b1173c931..0aab5793764 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 @@ -12,12 +12,15 @@ 'use-strict'; import {assertGenericTypeAnnotationHasExactlyOneTypeParameter} from '../parsers-commons'; - +import type {ParserType} from '../errors'; const { wrapNullable, unwrapNullable, emitMixedTypeAnnotation, + emitUnionTypeAnnotation, } = require('../parsers-commons.js'); +const {UnsupportedUnionTypeAnnotationParserError} = require('../errors'); +import type {UnionTypeAnnotationMemberType} from '../../CodegenSchema'; describe('wrapNullable', () => { describe('when nullable is true', () => { @@ -230,3 +233,434 @@ describe('emitMixedTypeAnnotation', () => { }); }); }); + +describe('emitUnionTypeAnnotation', () => { + const hasteModuleName = 'SampleTurboModule'; + + describe('when language is flow', () => { + const language: ParserType = 'Flow'; + + describe('when members type is numeric', () => { + const typeAnnotation = { + type: 'UnionTypeAnnotation', + types: [ + {type: 'NumberLiteralTypeAnnotation'}, + {type: 'NumberLiteralTypeAnnotation'}, + ], + }; + describe('when nullable is true', () => { + it('returns nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'NumberTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('when nullable is false', () => { + it('returns non nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'UnionTypeAnnotation', + memberType: 'NumberTypeAnnotation', + }; + + expect(result).toEqual(expected); + }); + }); + }); + + describe('when members type is string', () => { + const typeAnnotation = { + type: 'UnionTypeAnnotation', + types: [ + {type: 'StringLiteralTypeAnnotation'}, + {type: 'StringLiteralTypeAnnotation'}, + ], + }; + describe('when nullable is true', () => { + it('returns nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'StringTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('when nullable is false', () => { + it('returns non nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'UnionTypeAnnotation', + memberType: 'StringTypeAnnotation', + }; + + expect(result).toEqual(expected); + }); + }); + }); + + describe('when members type is object', () => { + const typeAnnotation = { + type: 'UnionTypeAnnotation', + types: [{type: 'ObjectTypeAnnotation'}, {type: 'ObjectTypeAnnotation'}], + }; + describe('when nullable is true', () => { + it('returns nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'ObjectTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('when nullable is false', () => { + it('returns non nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'UnionTypeAnnotation', + memberType: 'ObjectTypeAnnotation', + }; + + expect(result).toEqual(expected); + }); + }); + }); + + describe('when members type is mixed', () => { + const typeAnnotation = { + type: 'UnionTypeAnnotation', + types: [ + {type: 'NumberLiteralTypeAnnotation'}, + {type: 'StringLiteralTypeAnnotation'}, + {type: 'ObjectTypeAnnotation'}, + ], + }; + const unionTypes: UnionTypeAnnotationMemberType[] = [ + 'NumberTypeAnnotation', + 'StringTypeAnnotation', + 'ObjectTypeAnnotation', + ]; + describe('when nullable is true', () => { + it('throws an excpetion', () => { + const expected = new UnsupportedUnionTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + unionTypes, + language, + ); + + expect(() => { + emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + }).toThrow(expected); + }); + }); + + describe('when nullable is false', () => { + it('throws an excpetion', () => { + const expected = new UnsupportedUnionTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + unionTypes, + language, + ); + + expect(() => { + emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + }).toThrow(expected); + }); + }); + }); + }); + + describe('when language is typescript', () => { + const language: ParserType = 'TypeScript'; + + describe('when members type is numeric', () => { + const typeAnnotation = { + type: 'TSUnionType', + types: [ + { + type: 'TSLiteralType', + literal: {type: 'NumericLiteral'}, + }, + { + type: 'TSLiteralType', + literal: {type: 'NumericLiteral'}, + }, + ], + }; + describe('when nullable is true', () => { + it('returns nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'NumberTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('when nullable is false', () => { + it('returns non nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'UnionTypeAnnotation', + memberType: 'NumberTypeAnnotation', + }; + + expect(result).toEqual(expected); + }); + }); + }); + + describe('when members type is string', () => { + const typeAnnotation = { + type: 'TSUnionType', + types: [ + { + type: 'TSLiteralType', + literal: {type: 'StringLiteral'}, + }, + { + type: 'TSLiteralType', + literal: {type: 'StringLiteral'}, + }, + ], + }; + describe('when nullable is true', () => { + it('returns nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'StringTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('when nullable is false', () => { + it('returns non nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'UnionTypeAnnotation', + memberType: 'StringTypeAnnotation', + }; + + expect(result).toEqual(expected); + }); + }); + }); + + describe('when members type is object', () => { + const typeAnnotation = { + type: 'TSUnionType', + types: [ + { + type: 'TSLiteralType', + }, + { + type: 'TSLiteralType', + }, + ], + }; + describe('when nullable is true', () => { + it('returns nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'NullableTypeAnnotation', + typeAnnotation: { + type: 'UnionTypeAnnotation', + memberType: 'ObjectTypeAnnotation', + }, + }; + + expect(result).toEqual(expected); + }); + }); + + describe('when nullable is false', () => { + it('returns non nullable type annotation', () => { + const result = emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + + const expected = { + type: 'UnionTypeAnnotation', + memberType: 'ObjectTypeAnnotation', + }; + + expect(result).toEqual(expected); + }); + }); + }); + + describe('when members type is mixed', () => { + const typeAnnotation = { + type: 'TSUnionType', + types: [ + { + type: 'TSLiteralType', + literal: {type: 'NumericLiteral'}, + }, + { + type: 'TSLiteralType', + literal: {type: 'StringLiteral'}, + }, + { + type: 'TSLiteralType', + }, + ], + }; + const unionTypes = [ + 'NumberTypeAnnotation', + 'StringTypeAnnotation', + 'ObjectTypeAnnotation', + ]; + describe('when nullable is true', () => { + it('throws an excpetion', () => { + const expected = new UnsupportedUnionTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + unionTypes, + language, + ); + + expect(() => { + emitUnionTypeAnnotation( + true, + hasteModuleName, + typeAnnotation, + language, + ); + }).toThrow(expected); + }); + }); + + describe('when nullable is false', () => { + it('throws an excpetion', () => { + const expected = new UnsupportedUnionTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + unionTypes, + language, + ); + + expect(() => { + emitUnionTypeAnnotation( + false, + hasteModuleName, + typeAnnotation, + language, + ); + }).toThrow(expected); + }); + }); + }); + }); +}); diff --git a/packages/react-native-codegen/src/parsers/errors.js b/packages/react-native-codegen/src/parsers/errors.js index 3b85694a504..eab2dd36324 100644 --- a/packages/react-native-codegen/src/parsers/errors.js +++ b/packages/react-native-codegen/src/parsers/errors.js @@ -10,6 +10,8 @@ 'use strict'; +import type {UnionTypeAnnotationMemberType} from '../CodegenSchema'; + const invariant = require('invariant'); import type {Parser} from './parser'; export type ParserType = 'Flow' | 'TypeScript'; @@ -308,7 +310,7 @@ class UnsupportedUnionTypeAnnotationParserError extends ParserError { constructor( nativeModuleName: string, arrayElementTypeAST: $FlowFixMe, - types: string[], + types: UnionTypeAnnotationMemberType[], language: ParserType, ) { super( 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 747f9162c28..8cfd853d4fc 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -38,6 +38,7 @@ const { wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, emitMixedTypeAnnotation, + emitUnionTypeAnnotation, } = require('../../parsers-commons'); const { emitBoolean, @@ -375,29 +376,12 @@ function translateTypeAnnotation( } case 'UnionTypeAnnotation': { if (cxxOnly) { - // Remap literal names - const unionTypes = typeAnnotation.types - .map( - item => - item.type - .replace('NumberLiteralTypeAnnotation', 'NumberTypeAnnotation') - .replace('StringLiteralTypeAnnotation', 'StringTypeAnnotation'), - // ObjectAnnotation is already 'correct' - ) - .filter((value, index, self) => self.indexOf(value) === index); - // Only support unionTypes of the same kind - if (unionTypes.length > 1) { - throw new UnsupportedUnionTypeAnnotationParserError( - hasteModuleName, - typeAnnotation, - unionTypes, - language, - ); - } - return wrapNullable(nullable, { - type: 'UnionTypeAnnotation', - memberType: unionTypes[0], - }); + return emitUnionTypeAnnotation( + nullable, + hasteModuleName, + typeAnnotation, + language, + ); } // Fallthrough } diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 73b5061f0a9..eda3b3ef299 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -16,8 +16,13 @@ import type { NativeModuleTypeAnnotation, Nullable, NativeModuleMixedTypeAnnotation, + UnionTypeAnnotationMemberType, + NativeModuleUnionTypeAnnotation, } from '../CodegenSchema.js'; -const {IncorrectlyParameterizedGenericParserError} = require('./errors'); +const { + IncorrectlyParameterizedGenericParserError, + UnsupportedUnionTypeAnnotationParserError, +} = require('./errors'); import type {ParserType} from './errors'; const invariant = require('invariant'); @@ -100,10 +105,61 @@ function emitMixedTypeAnnotation( }); } +function remapUnionTypeAnnotationMemberNames( + types: $FlowFixMe, + language: ParserType, +): UnionTypeAnnotationMemberType[] { + const remapLiteral = (item: $FlowFixMe) => { + if (language === 'Flow') { + return item.type + .replace('NumberLiteralTypeAnnotation', 'NumberTypeAnnotation') + .replace('StringLiteralTypeAnnotation', 'StringTypeAnnotation'); + } + + return item.literal + ? item.literal.type + .replace('NumericLiteral', 'NumberTypeAnnotation') + .replace('StringLiteral', 'StringTypeAnnotation') + : 'ObjectTypeAnnotation'; + }; + + return types + .map(remapLiteral) + .filter((value, index, self) => self.indexOf(value) === index); +} + +function emitUnionTypeAnnotation( + nullable: boolean, + hasteModuleName: string, + typeAnnotation: $FlowFixMe, + language: ParserType, +): Nullable { + const unionTypes = remapUnionTypeAnnotationMemberNames( + typeAnnotation.types, + language, + ); + + // Only support unionTypes of the same kind + if (unionTypes.length > 1) { + throw new UnsupportedUnionTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + unionTypes, + language, + ); + } + + return wrapNullable(nullable, { + type: 'UnionTypeAnnotation', + memberType: unionTypes[0], + }); +} + module.exports = { wrapModuleSchema, unwrapNullable, wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, emitMixedTypeAnnotation, + emitUnionTypeAnnotation, }; 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 097a505f889..9c6f6080faa 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -38,6 +38,7 @@ const { wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, emitMixedTypeAnnotation, + emitUnionTypeAnnotation, } = require('../../parsers-commons'); const { emitBoolean, @@ -60,7 +61,6 @@ const { UnsupportedTypeAnnotationParserError, UnsupportedFunctionParamTypeAnnotationParserError, UnsupportedEnumDeclarationParserError, - UnsupportedUnionTypeAnnotationParserError, UnsupportedObjectPropertyTypeAnnotationParserError, IncorrectModuleRegistryCallArgumentTypeParserError, } = require('../../errors.js'); @@ -390,29 +390,12 @@ function translateTypeAnnotation( } case 'TSUnionType': { if (cxxOnly) { - // Remap literal names - const unionTypes = typeAnnotation.types - .map(item => - item.literal - ? item.literal.type - .replace('NumericLiteral', 'NumberTypeAnnotation') - .replace('StringLiteral', 'StringTypeAnnotation') - : 'ObjectTypeAnnotation', - ) - .filter((value, index, self) => self.indexOf(value) === index); - // Only support unionTypes of the same kind - if (unionTypes.length > 1) { - throw new UnsupportedUnionTypeAnnotationParserError( - hasteModuleName, - typeAnnotation, - unionTypes, - language, - ); - } - return wrapNullable(nullable, { - type: 'UnionTypeAnnotation', - memberType: unionTypes[0], - }); + return emitUnionTypeAnnotation( + nullable, + hasteModuleName, + typeAnnotation, + language, + ); } // Fallthrough }