From 0627cd69cd39a9f88df24e4d8d883e552d88dfe8 Mon Sep 17 00:00:00 2001 From: youedd Date: Wed, 26 Oct 2022 10:02:01 -0700 Subject: [PATCH] Refactor translate UniontypeAnnotation (#35050) Summary: Part of https://github.com/facebook/react-native/issues/34872 > [Assigned to youedd] This task is more advanced than the others Create a function to unify the [unionType](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/flow/modules/index.js#L372-L396) and the [TSUnionType](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/typescript/modules/index.js#L407-L431). This task may share some logic from the previous task. The function should accept a ParserType parameter to implement the proper logic to extract the memberType variable. Ideally, we should create a couple of supporting functions to implement those logics. ## Changelog [Internal] [Changed] - Refactor translate UniontypeAnnotation Pull Request resolved: https://github.com/facebook/react-native/pull/35050 Test Plan: `yarn jest react-native-codegen` ![image](https://user-images.githubusercontent.com/19575877/197334084-2daeed7e-8e87-4140-b8c3-07f2de3f0496.png) Reviewed By: cortinico Differential Revision: D40637299 Pulled By: cipolleschi fbshipit-source-id: 1490001a3398c3af79d465c40de3c3687f058523 --- .../parsers/__tests__/parsers-commons-test.js | 436 +++++++++++++++++- .../src/parsers/errors.js | 4 +- .../src/parsers/flow/modules/index.js | 30 +- .../src/parsers/parsers-commons.js | 58 ++- .../src/parsers/typescript/modules/index.js | 31 +- 5 files changed, 509 insertions(+), 50 deletions(-) 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 }