From 5744b219c75ab6c2963d962d07edd6bf2f733662 Mon Sep 17 00:00:00 2001 From: Pranav Yadav Date: Wed, 9 Nov 2022 02:11:17 -0800 Subject: [PATCH] Extract `tryParse` (Flow, TypeScript) lambda into `parseObjectProperty` fn (#35076) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This PR is a task of https://github.com/facebook/react-native/issues/34872 > Extract the content of the tryParse (Flow, TypeScript)lambda into a proper `parseObjectProperty` function into the parsers-commons.js file. also, - added new helper fn `isObjectProperty` in `parsers-commons.js` file. - added tests for `isObjectProperty` and `parseObjectProperty` fn 's ## Changelog [Internal] [Changed] - Extracted the content of the `tryParse` ([Flow](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/flow/modules/index.js#L292-L337), [TypeScript](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/typescript/modules/index.js#L306-L351)) lambda into a proper `parseObjectProperty` fn into the `parsers-commons.js` file. Pull Request resolved: https://github.com/facebook/react-native/pull/35076 Test Plan: - run ```bash yarn lint && yarn flow && yarn test-ci ``` - and ensure everything is � image Reviewed By: cortinico Differential Revision: D40797241 Pulled By: cipolleschi fbshipit-source-id: 48b8900ead70d5eda2496f9ce044c11a9599a177 --- .../parsers/__tests__/parsers-commons-test.js | 214 +++++++++++++++++- .../src/parsers/error-utils.js | 2 +- .../src/parsers/flow/components/schema.js | 2 +- .../src/parsers/flow/modules/index.js | 76 ++----- .../src/parsers/parsers-commons.js | 119 ++++++++-- .../src/parsers/parsers-primitives.js | 2 +- .../src/parsers/typescript/modules/index.js | 76 ++----- 7 files changed, 348 insertions(+), 143 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 a8da4a6fd07262..1cb8a15126f562 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 @@ -11,19 +11,29 @@ 'use-strict'; -import {assertGenericTypeAnnotationHasExactlyOneTypeParameter} from '../parsers-commons'; -import type {ParserType} from '../errors'; -const { +import { + assertGenericTypeAnnotationHasExactlyOneTypeParameter, + isObjectProperty, + parseObjectProperty, wrapNullable, unwrapNullable, emitUnionTypeAnnotation, -} = require('../parsers-commons.js'); -const {UnsupportedUnionTypeAnnotationParserError} = require('../errors'); +} from '../parsers-commons'; +import type {ParserType} from '../errors'; import type {UnionTypeAnnotationMemberType} from '../../CodegenSchema'; + +const { + UnsupportedUnionTypeAnnotationParserError, + UnsupportedObjectPropertyTypeAnnotationParserError, +} = require('../errors'); + import {MockedParser} from '../parserMock'; const parser = new MockedParser(); +const flowTranslateTypeAnnotation = require('../flow/modules/index'); +const typeScriptTranslateTypeAnnotation = require('../typescript/modules/index'); + describe('wrapNullable', () => { describe('when nullable is true', () => { it('returns nullable type annotation', () => { @@ -189,6 +199,200 @@ describe('assertGenericTypeAnnotationHasExactlyOneTypeParameter', () => { }); }); +describe('isObjectProperty', () => { + const propertyStub = { + /* type: 'notObjectTypeProperty', */ + typeAnnotation: { + typeAnnotation: 'wrongTypeAnnotation', + }, + value: 'wrongValue', + name: 'wrongName', + }; + + describe("when 'language' is 'Flow'", () => { + const language: ParserType = 'Flow'; + it("returns 'true' if 'property.type' is 'ObjectTypeProperty'", () => { + const result = isObjectProperty( + { + type: 'ObjectTypeProperty', + ...propertyStub, + }, + language, + ); + 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'", () => { + const result = isObjectProperty( + { + type: 'notObjectTypeProperty', + ...propertyStub, + }, + language, + ); + expect(result).toEqual(false); + }); + }); + + describe("when 'language' is 'TypeScript'", () => { + const language: ParserType = 'TypeScript'; + it("returns 'true' if 'property.type' is 'TSPropertySignature'", () => { + const result = isObjectProperty( + { + type: 'TSPropertySignature', + ...propertyStub, + }, + language, + ); + 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'", () => { + const result = isObjectProperty( + { + type: 'notTSPropertySignature', + ...propertyStub, + }, + language, + ); + expect(result).toEqual(false); + }); + }); +}); + +describe('parseObjectProperty', () => { + const moduleName = 'testModuleName'; + const types = {['wrongName']: 'wrongType'}; + const aliasMap = {}; + const tryParse = () => null; + const cxxOnly = false; + const nullable = true; + + describe("when 'language' is 'Flow'", () => { + const language: ParserType = 'Flow'; + it("throws an 'UnsupportedObjectPropertyTypeAnnotationParserError' error if 'property.type' is not 'ObjectTypeProperty' or 'ObjectTypeIndexer'.", () => { + const property = { + type: 'notObjectTypeProperty', + typeAnnotation: { + type: 'notObjectTypeProperty', + typeAnnotation: 'wrongTypeAnnotation', + }, + value: 'wrongValue', + name: 'wrongName', + }; + const expected = new UnsupportedObjectPropertyTypeAnnotationParserError( + moduleName, + property, + property.type, + language, + ); + expect(() => + parseObjectProperty( + property, + moduleName, + types, + aliasMap, + tryParse, + cxxOnly, + language, + nullable, + flowTranslateTypeAnnotation, + ), + ).toThrow(expected); + }); + }); + + describe("when 'language' is 'TypeScript'", () => { + const language: ParserType = 'TypeScript'; + it("throws an 'UnsupportedObjectPropertyTypeAnnotationParserError' error if 'property.type' is not 'TSPropertySignature' or 'TSIndexSignature'.", () => { + const property = { + type: 'notTSPropertySignature', + typeAnnotation: { + typeAnnotation: 'wrongTypeAnnotation', + }, + value: 'wrongValue', + name: 'wrongName', + }; + const expected = new UnsupportedObjectPropertyTypeAnnotationParserError( + moduleName, + property, + property.type, + language, + ); + expect(() => + parseObjectProperty( + property, + moduleName, + types, + aliasMap, + tryParse, + cxxOnly, + language, + nullable, + typeScriptTranslateTypeAnnotation, + ), + ).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, + language, + nullable, + typeScriptTranslateTypeAnnotation, + ); + const expected = { + name: 'testName', + optional: false, + typeAnnotation: wrapNullable(nullable, { + type: 'GenericObjectTypeAnnotation', + }), + }; + expect(result).toEqual(expected); + }); + }); +}); + describe('emitUnionTypeAnnotation', () => { const hasteModuleName = 'SampleTurboModule'; diff --git a/packages/react-native-codegen/src/parsers/error-utils.js b/packages/react-native-codegen/src/parsers/error-utils.js index 2d53c82c9e6f55..2584e79dc48d46 100644 --- a/packages/react-native-codegen/src/parsers/error-utils.js +++ b/packages/react-native-codegen/src/parsers/error-utils.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict-local + * @flow strict * @format */ diff --git a/packages/react-native-codegen/src/parsers/flow/components/schema.js b/packages/react-native-codegen/src/parsers/flow/components/schema.js index ab165031cd25fb..ec9447251c1d35 100644 --- a/packages/react-native-codegen/src/parsers/flow/components/schema.js +++ b/packages/react-native-codegen/src/parsers/flow/components/schema.js @@ -4,8 +4,8 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @format * @flow strict + * @format */ 'use strict'; 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 9ff2dcb5a0a6a0..c03e150b5dfb13 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -26,13 +26,13 @@ import type {ParserErrorCapturer, TypeDeclarationMap} from '../../utils'; import type {NativeModuleTypeAnnotation} from '../../../CodegenSchema.js'; const {nullGuard} = require('../../parsers-utils'); -const {throwIfMoreThanOneModuleRegistryCalls} = require('../../error-utils'); -const {visit, isModuleRegistryCall} = require('../../utils'); +const {visit, verifyPlatforms, isModuleRegistryCall} = require('../../utils'); const {resolveTypeAnnotation, getTypes} = require('../utils.js'); const { unwrapNullable, wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, + parseObjectProperty, emitUnionTypeAnnotation, translateDefault, } = require('../../parsers-commons'); @@ -62,15 +62,13 @@ const { IncorrectModuleRegistryCallArgumentTypeParserError, } = require('../../errors.js'); -const {verifyPlatforms} = require('../../utils'); - const { throwIfUnsupportedFunctionReturnTypeAnnotationParserError, throwIfModuleInterfaceNotFound, throwIfModuleInterfaceIsMisnamed, - throwIfPropertyValueTypeIsUnsupported, throwIfUnusedModuleInterfaceParserError, throwIfWrongNumberOfCallExpressionArgs, + throwIfMoreThanOneModuleRegistryCalls, throwIfIncorrectModuleRegistryCallTypeParameterParserError, throwIfUntypedModule, throwIfModuleTypeIsUnsupported, @@ -79,7 +77,6 @@ const { } = require('../../error-utils'); const {FlowParser} = require('../parser.js'); -const {getKeyName} = require('../../parsers-commons'); const language = 'Flow'; const parser = new FlowParser(); @@ -262,61 +259,17 @@ function translateTypeAnnotation( .map>>( property => { return tryParse(() => { - if ( - property.type !== 'ObjectTypeProperty' && - property.type !== 'ObjectTypeIndexer' - ) { - throw new UnsupportedObjectPropertyTypeAnnotationParserError( - hasteModuleName, - property, - property.type, - language, - ); - } - - 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( - hasteModuleName, - property.value, - types, - aliasMap, - tryParse, - cxxOnly, - ), - ); - - if ( - propertyTypeAnnotation.type === 'FunctionTypeAnnotation' || - propertyTypeAnnotation.type === 'PromiseTypeAnnotation' || - propertyTypeAnnotation.type === 'VoidTypeAnnotation' - ) { - throwIfPropertyValueTypeIsUnsupported( - hasteModuleName, - property.value, - property.key, - propertyTypeAnnotation.type, - language, - ); - } else { - return { - name, - optional, - typeAnnotation: wrapNullable( - isPropertyNullable, - propertyTypeAnnotation, - ), - }; - } + return parseObjectProperty( + property, + hasteModuleName, + types, + aliasMap, + tryParse, + cxxOnly, + language, + nullable, + translateTypeAnnotation, + ); }); }, ) @@ -596,4 +549,5 @@ function buildModuleSchema( module.exports = { buildModuleSchema, + flowTranslateTypeAnnotation: translateTypeAnnotation, }; diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 0d1af17fc526ec..4c14a5df546392 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -4,37 +4,39 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @format * @flow strict + * @format */ 'use strict'; import type { SchemaType, + NamedShape, NativeModuleSchema, NativeModuleTypeAnnotation, - Nullable, + NativeModuleAliasMap, UnionTypeAnnotationMemberType, + NativeModuleEnumDeclaration, + NativeModuleBaseTypeAnnotation, NativeModuleUnionTypeAnnotation, + Nullable, } from '../CodegenSchema.js'; +import type {ParserType} from './errors'; +import type {ParserErrorCapturer, TypeDeclarationMap} from './utils'; +import type {Parser} from './parser'; + const { MissingTypeParameterGenericParserError, MoreThanOneTypeParameterGenericParserError, - UnsupportedUnionTypeAnnotationParserError, -} = require('./errors'); -import type {ParserType} from './errors'; -const { UnsupportedObjectPropertyTypeAnnotationParserError, -} = require('./errors'); -const invariant = require('invariant'); -import type {TypeDeclarationMap} from './utils'; -const { + UnsupportedUnionTypeAnnotationParserError, UnsupportedEnumDeclarationParserError, UnsupportedGenericParserError, } = require('./errors'); -import type {Parser} from './parser'; -import type {NativeModuleEnumDeclaration} from '../CodegenSchema'; +const {throwIfPropertyValueTypeIsUnsupported} = require('./error-utils'); + +const invariant = require('invariant'); function wrapModuleSchema( nativeModuleSchema: NativeModuleSchema, @@ -103,6 +105,95 @@ function assertGenericTypeAnnotationHasExactlyOneTypeParameter( } } +function isObjectProperty(property: $FlowFixMe, language: ParserType): boolean { + switch (language) { + case 'Flow': + return ( + property.type === 'ObjectTypeProperty' || + property.type === 'ObjectTypeIndexer' + ); + case 'TypeScript': + return ( + property.type === 'TSPropertySignature' || + property.type === 'TSIndexSignature' + ); + default: + return false; + } +} + +function parseObjectProperty( + property: $FlowFixMe, + hasteModuleName: string, + types: TypeDeclarationMap, + aliasMap: {...NativeModuleAliasMap}, + tryParse: ParserErrorCapturer, + cxxOnly: boolean, + language: ParserType, + nullable: boolean, + translateTypeAnnotation: $FlowFixMe, +): NamedShape> { + if (!isObjectProperty(property, language)) { + throw new UnsupportedObjectPropertyTypeAnnotationParserError( + hasteModuleName, + property, + property.type, + language, + ); + } + + const {optional = false} = property; + const name = getKeyName(property, hasteModuleName, language); + 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( + translateTypeAnnotation( + hasteModuleName, + languageTypeAnnotation, + types, + aliasMap, + tryParse, + cxxOnly, + ), + ); + + if ( + propertyTypeAnnotation.type === 'FunctionTypeAnnotation' || + propertyTypeAnnotation.type === 'PromiseTypeAnnotation' || + propertyTypeAnnotation.type === 'VoidTypeAnnotation' + ) { + throwIfPropertyValueTypeIsUnsupported( + hasteModuleName, + languageTypeAnnotation, + property.key, + propertyTypeAnnotation.type, + language, + ); + } + + return { + name, + optional, + typeAnnotation: wrapNullable(isPropertyNullable, propertyTypeAnnotation), + }; +} + function remapUnionTypeAnnotationMemberNames( types: $FlowFixMe, language: ParserType, @@ -221,7 +312,9 @@ module.exports = { unwrapNullable, wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, + isObjectProperty, + parseObjectProperty, emitUnionTypeAnnotation, - getKeyName, translateDefault, + getKeyName, }; diff --git a/packages/react-native-codegen/src/parsers/parsers-primitives.js b/packages/react-native-codegen/src/parsers/parsers-primitives.js index 2fe7f984cd0d32..d0f449ed0aea59 100644 --- a/packages/react-native-codegen/src/parsers/parsers-primitives.js +++ b/packages/react-native-codegen/src/parsers/parsers-primitives.js @@ -4,8 +4,8 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @format * @flow strict-local + * @format */ 'use strict'; 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 37b9b70a2ba506..e3f9d2c7c8e18d 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -17,21 +17,20 @@ import type { NativeModuleBaseTypeAnnotation, NativeModuleFunctionTypeAnnotation, NativeModulePropertyShape, + NativeModuleTypeAnnotation, NativeModuleSchema, Nullable, } from '../../../CodegenSchema.js'; import type {ParserErrorCapturer, TypeDeclarationMap} from '../../utils'; -import type {NativeModuleTypeAnnotation} from '../../../CodegenSchema.js'; const {nullGuard} = require('../../parsers-utils'); - -const {throwIfMoreThanOneModuleRegistryCalls} = require('../../error-utils'); const {visit, isModuleRegistryCall} = require('../../utils'); const {resolveTypeAnnotation, getTypes} = require('../utils.js'); const { unwrapNullable, wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, + parseObjectProperty, emitUnionTypeAnnotation, translateDefault, } = require('../../parsers-commons'); @@ -56,7 +55,6 @@ const { UnsupportedArrayElementTypeAnnotationParserError, UnsupportedGenericParserError, UnsupportedTypeAnnotationParserError, - UnsupportedObjectPropertyTypeAnnotationParserError, IncorrectModuleRegistryCallArgumentTypeParserError, } = require('../../errors.js'); @@ -64,18 +62,17 @@ const {verifyPlatforms} = require('../../utils'); const { throwIfUntypedModule, - throwIfPropertyValueTypeIsUnsupported, throwIfModuleTypeIsUnsupported, throwIfUnusedModuleInterfaceParserError, throwIfModuleInterfaceNotFound, throwIfModuleInterfaceIsMisnamed, throwIfWrongNumberOfCallExpressionArgs, + throwIfMoreThanOneModuleRegistryCalls, throwIfMoreThanOneModuleInterfaceParserError, throwIfIncorrectModuleRegistryCallTypeParameterParserError, } = require('../../error-utils'); const {TypeScriptParser} = require('../parser'); -const {getKeyName} = require('../../parsers-commons'); const language = 'TypeScript'; const parser = new TypeScriptParser(); @@ -268,61 +265,17 @@ function translateTypeAnnotation( .map>>( property => { return tryParse(() => { - if ( - property.type !== 'TSPropertySignature' && - property.type !== 'TSIndexSignature' - ) { - throw new UnsupportedObjectPropertyTypeAnnotationParserError( - hasteModuleName, - property, - property.type, - language, - ); - } - - 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( - hasteModuleName, - property.typeAnnotation.typeAnnotation, - types, - aliasMap, - tryParse, - cxxOnly, - ), - ); - - if ( - propertyTypeAnnotation.type === 'FunctionTypeAnnotation' || - propertyTypeAnnotation.type === 'PromiseTypeAnnotation' || - propertyTypeAnnotation.type === 'VoidTypeAnnotation' - ) { - throwIfPropertyValueTypeIsUnsupported( - hasteModuleName, - property.typeAnnotation.typeAnnotation, - property.key, - propertyTypeAnnotation.type, - language, - ); - } else { - return { - name, - optional, - typeAnnotation: wrapNullable( - isPropertyNullable, - propertyTypeAnnotation, - ), - }; - } + return parseObjectProperty( + property, + hasteModuleName, + types, + aliasMap, + tryParse, + cxxOnly, + language, + nullable, + translateTypeAnnotation, + ); }); }, ) @@ -605,4 +558,5 @@ function buildModuleSchema( module.exports = { buildModuleSchema, + typeScriptTranslateTypeAnnotation: translateTypeAnnotation, };