From 8d807dfbc30d46c3c09fd5f745ea222b9624c661 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Thu, 1 Oct 2020 19:26:14 -0700 Subject: [PATCH] Introduce NullableTypeAnnotation for Flow Module Parser Summary: Previously, all our type annotations contained a `nullable` property. This diff removes that property from all our NativeModule type annotations, and instead introduces a `NullableTypeAnnotation`. **Some Benefits:** - In all our serialization functions, we use Flow exhaustive checking to ensure that all type-annotations can be serialized. Since nullability is now recorded as a type annotation, Flow will ensure we always explicitly handle nullability. Previously, with nullability as a property, we could ignore it without any feedback from flow. - This aligns the NativeModule schema with the ESTree spec. - After this diff, we're one step closer to sharing type annotations with Codegen's schema. Many NativeModule type annotations now have the same shape as their Codegen counterparts. They will be merged in a subsequent diff. **Downsides:** - If you want to check whether a type annotation is of type `T`, you have to remember to unwrap the type annotation *yourself*. Flow won't warn you if you forget to unwrap the type, which can lead to incomplete handling to nullable types in our generators. - When you're creating type annotations in code, previously, you *had* to specify nullability, since it was a property on all type annotation objects. Now, it's very possible for you to forget to wrap the type annotation, which will just lead to nullability bugs. **Notes:** - In the scheam, exported type annotations are *always* required. They can be made nullable using the new `Nullable` genric type. Changelog: [Internal] Reviewed By: fkgozali Differential Revision: D24026887 fbshipit-source-id: 9e71e2c6102dc506824403dbb712488ca8507d08 --- .../react-native-codegen/src/CodegenSchema.js | 45 +-- .../module-parser-snapshot-test.js.snap | 358 +++++------------- .../__tests__/module-parser-e2e-test.js | 313 ++++++++++----- .../src/parsers/flow/modules/index.js | 164 ++++---- .../src/parsers/flow/modules/utils.js | 45 +++ 5 files changed, 469 insertions(+), 456 deletions(-) create mode 100644 packages/react-native-codegen/src/parsers/flow/modules/utils.js diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 7e35b52207a..a341f1757d3 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -258,13 +258,7 @@ export type SchemaType = $ReadOnly<{| /** * NativeModule Types - * - * TODO(T71923114): Remove nullable from NativeModule type annotations. - * This is necessary for us to start unifying the notion of a "type - * annotation" across the codegen schema as a whole. */ -export type Required = $ReadOnly<{...T, nullable: false}>; - export type NativeModuleSchema = $ReadOnly<{| // We only support aliases to Objects aliases: NativeModuleAliasMap, @@ -272,26 +266,36 @@ export type NativeModuleSchema = $ReadOnly<{| |}>; export type NativeModuleAliasMap = $ReadOnly<{| - [aliasName: string]: Required, + [aliasName: string]: NativeModuleObjectTypeAnnotation, |}>; export type NativeModulePropertySchema = $ReadOnly<{| name: string, optional: boolean, - typeAnnotation: NativeModuleFunctionTypeAnnotation, + typeAnnotation: Nullable, +|}>; + +export type Nullable<+T: NativeModuleTypeAnnotation> = + | NullableTypeAnnotation + | T; + +export type NullableTypeAnnotation< + +T: NativeModuleTypeAnnotation, +> = $ReadOnly<{| + type: 'NullableTypeAnnotation', + typeAnnotation: T, |}>; export type NativeModuleFunctionTypeAnnotation = $ReadOnly<{| type: 'FunctionTypeAnnotation', params: $ReadOnlyArray, - returnTypeAnnotation: NativeModuleReturnTypeAnnotation, - nullable: boolean, + returnTypeAnnotation: Nullable, |}>; export type NativeModuleMethodParamSchema = $ReadOnly<{| name: string, optional: boolean, - typeAnnotation: NativeModuleParamTypeAnnotation, + typeAnnotation: Nullable, |}>; export type NativeModuleObjectTypeAnnotation = $ReadOnly<{| @@ -300,14 +304,13 @@ export type NativeModuleObjectTypeAnnotation = $ReadOnly<{| $ReadOnly<{| name: string, optional: boolean, - typeAnnotation: NativeModuleBaseTypeAnnotation, + typeAnnotation: Nullable, |}>, >, - nullable: boolean, |}>; export type NativeModuleArrayTypeAnnotation< - T = NativeModuleBaseTypeAnnotation, + +T: Nullable, > = $ReadOnly<{| type: 'ArrayTypeAnnotation', /** @@ -315,64 +318,52 @@ export type NativeModuleArrayTypeAnnotation< * invalid Array ElementTypes. Then, make the elementType required. */ elementType?: T, - nullable: boolean, |}>; export type NativeModuleStringTypeAnnotation = $ReadOnly<{| type: 'StringTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleNumberTypeAnnotation = $ReadOnly<{| type: 'NumberTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleInt32TypeAnnotation = $ReadOnly<{| type: 'Int32TypeAnnotation', - nullable: boolean, |}>; export type NativeModuleDoubleTypeAnnotation = $ReadOnly<{| type: 'DoubleTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleFloatTypeAnnotation = $ReadOnly<{| type: 'FloatTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleBooleanTypeAnnotation = $ReadOnly<{| type: 'BooleanTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleGenericObjectTypeAnnotation = $ReadOnly<{| type: 'GenericObjectTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleReservedFunctionValueTypeAnnotation = $ReadOnly<{| type: 'ReservedFunctionValueTypeAnnotation', name: ReservedFunctionValueTypeName, - nullable: boolean, |}>; export type NativeModuleTypeAliasTypeAnnotation = $ReadOnly<{| type: 'TypeAliasTypeAnnotation', name: string, - nullable: boolean, |}>; export type NativeModulePromiseTypeAnnotation = $ReadOnly<{| type: 'PromiseTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleVoidTypeAnnotation = $ReadOnly<{| type: 'VoidTypeAnnotation', - nullable: boolean, |}>; export type NativeModuleBaseTypeAnnotation = @@ -385,7 +376,7 @@ export type NativeModuleBaseTypeAnnotation = | NativeModuleGenericObjectTypeAnnotation | NativeModuleReservedFunctionValueTypeAnnotation | NativeModuleTypeAliasTypeAnnotation - | NativeModuleArrayTypeAnnotation<> + | NativeModuleArrayTypeAnnotation> | NativeModuleObjectTypeAnnotation; export type NativeModuleParamTypeAnnotation = 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 64b677c38d8..f5ec83220a0 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 @@ -34,28 +34,27 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES SampleTurboModule: { aliases: { ObjectAlias: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'x', optional: false, - typeAnnotation: { nullable: false, type: 'NumberTypeAnnotation' } + typeAnnotation: { type: 'NumberTypeAnnotation' } }, { name: 'y', optional: false, - typeAnnotation: { nullable: false, type: 'NumberTypeAnnotation' } + typeAnnotation: { type: 'NumberTypeAnnotation' } }, { name: 'label', optional: false, - typeAnnotation: { nullable: false, type: 'StringTypeAnnotation' } + typeAnnotation: { type: 'StringTypeAnnotation' } }, { name: 'truthy', optional: false, - typeAnnotation: { nullable: false, type: 'BooleanTypeAnnotation' } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } ] } @@ -66,15 +65,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'NumberTypeAnnotation' } + typeAnnotation: { type: 'NumberTypeAnnotation' } } - ], - nullable: false + ] } }, { @@ -82,9 +80,8 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, - params: [], - nullable: false + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, + params: [] } }, { @@ -93,13 +90,12 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'a', optional: false, - typeAnnotation: { nullable: false, type: 'NumberTypeAnnotation' } + typeAnnotation: { type: 'NumberTypeAnnotation' } } ] }, @@ -108,13 +104,11 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES name: 'a', optional: false, typeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'NumberTypeAnnotation' } + elementType: { type: 'NumberTypeAnnotation' } } } - ], - nullable: false + ] } }, { @@ -122,19 +116,17 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'StringTypeAnnotation' }, + returnTypeAnnotation: { type: 'StringTypeAnnotation' }, params: [ { name: 'a', optional: false, typeAnnotation: { - nullable: false, type: 'TypeAliasTypeAnnotation', name: 'ObjectAlias' } } - ], - nullable: false + ] } } ] @@ -159,22 +151,19 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WI typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'StringTypeAnnotation' } + elementType: { type: 'StringTypeAnnotation' } }, params: [ { name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'StringTypeAnnotation' } + elementType: { type: 'StringTypeAnnotation' } } } - ], - nullable: false + ] } } ] @@ -198,15 +187,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ARRAY_WI optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'ArrayTypeAnnotation' }, + returnTypeAnnotation: { type: 'ArrayTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'ArrayTypeAnnotation' } + typeAnnotation: { type: 'ArrayTypeAnnotation' } } - ], - nullable: false + ] } } ] @@ -231,22 +219,19 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_BASIC_AR typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'StringTypeAnnotation' } + elementType: { type: 'StringTypeAnnotation' } }, params: [ { name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'StringTypeAnnotation' } + elementType: { type: 'StringTypeAnnotation' } } } - ], - nullable: false + ] } }, { @@ -255,22 +240,19 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_BASIC_AR typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'StringTypeAnnotation' } + elementType: { type: 'StringTypeAnnotation' } }, params: [ { name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'StringTypeAnnotation' } + elementType: { type: 'StringTypeAnnotation' } } } - ], - nullable: false + ] } } ] @@ -294,15 +276,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_BASIC_PA optional: true, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'BooleanTypeAnnotation' } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } - ], - nullable: false + ] } }, { @@ -310,15 +291,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_BASIC_PA optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'NumberTypeAnnotation' } + typeAnnotation: { type: 'NumberTypeAnnotation' } } - ], - nullable: false + ] } }, { @@ -326,15 +306,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_BASIC_PA optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'StringTypeAnnotation' } + typeAnnotation: { type: 'StringTypeAnnotation' } } - ], - nullable: false + ] } }, { @@ -342,15 +321,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_BASIC_PA optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'StringTypeAnnotation' } + typeAnnotation: { type: 'StringTypeAnnotation' } } - ], - nullable: false + ] } } ] @@ -374,45 +352,35 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_CALLBACK optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'callback', optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'value', optional: false, - typeAnnotation: { - nullable: false, - type: 'StringTypeAnnotation' - } + typeAnnotation: { type: 'StringTypeAnnotation' } }, { name: 'arr', optional: false, typeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { - nullable: false, - type: 'StringTypeAnnotation' - } + elementType: { type: 'StringTypeAnnotation' } } } } - ], - nullable: false + ] } } - ], - nullable: false + ] } } ] @@ -437,15 +405,12 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { nullable: false, type: 'StringTypeAnnotation' } + elementType: { type: 'StringTypeAnnotation' } } } }, @@ -454,32 +419,23 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ArrayTypeAnnotation', - elementType: { - nullable: false, - type: 'StringTypeAnnotation' - } + elementType: { type: 'StringTypeAnnotation' } } } } } } } - ], - nullable: false + ] } } ] @@ -504,23 +460,18 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } ] } @@ -532,23 +483,18 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } ] } @@ -556,8 +502,7 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ ] } } - ], - nullable: false + ] } }, { @@ -566,23 +511,18 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } ] } @@ -594,23 +534,18 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } ] } @@ -618,8 +553,7 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ ] } } - ], - nullable: false + ] } }, { @@ -627,31 +561,23 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { - nullable: false, - type: 'GenericObjectTypeAnnotation' - }, + returnTypeAnnotation: { type: 'GenericObjectTypeAnnotation' }, params: [ { name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'a', optional: false, - typeAnnotation: { - nullable: false, - type: 'StringTypeAnnotation' - } + typeAnnotation: { type: 'StringTypeAnnotation' } } ] } } - ], - nullable: false + ] } }, { @@ -660,26 +586,20 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ArrayTypeAnnotation', elementType: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } ] } @@ -692,23 +612,18 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ name: 'arg', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'const1', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } } ] } @@ -716,8 +631,7 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ ] } } - ], - nullable: false + ] } } ] @@ -742,54 +656,40 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'isTesting', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } }, { name: 'reactNativeVersion', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'major', optional: false, - typeAnnotation: { - nullable: false, - type: 'NumberTypeAnnotation' - } + typeAnnotation: { type: 'NumberTypeAnnotation' } }, { name: 'minor', optional: false, - typeAnnotation: { - nullable: false, - type: 'NumberTypeAnnotation' - } + typeAnnotation: { type: 'NumberTypeAnnotation' } }, { name: 'patch', optional: true, - typeAnnotation: { - nullable: false, - type: 'NumberTypeAnnotation' - } + typeAnnotation: { type: 'NumberTypeAnnotation' } }, { name: 'prerelease', optional: false, typeAnnotation: { - nullable: true, - type: 'NumberTypeAnnotation' + type: 'NullableTypeAnnotation', + typeAnnotation: { type: 'NumberTypeAnnotation' } } } ] @@ -798,30 +698,26 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_COMPLEX_ { name: 'forceTouchAvailable', optional: false, - typeAnnotation: { - nullable: false, - type: 'BooleanTypeAnnotation' - } + typeAnnotation: { type: 'BooleanTypeAnnotation' } }, { name: 'osVersion', optional: false, - typeAnnotation: { nullable: false, type: 'StringTypeAnnotation' } + typeAnnotation: { type: 'StringTypeAnnotation' } }, { name: 'systemName', optional: false, - typeAnnotation: { nullable: false, type: 'StringTypeAnnotation' } + typeAnnotation: { type: 'StringTypeAnnotation' } }, { name: 'interfaceIdiom', optional: false, - typeAnnotation: { nullable: false, type: 'StringTypeAnnotation' } + typeAnnotation: { type: 'StringTypeAnnotation' } } ] }, - params: [], - nullable: false + params: [] } } ] @@ -845,15 +741,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_FLOAT_AN optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'Int32TypeAnnotation' }, + returnTypeAnnotation: { type: 'Int32TypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'Int32TypeAnnotation' } + typeAnnotation: { type: 'Int32TypeAnnotation' } } - ], - nullable: false + ] } }, { @@ -861,15 +756,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_FLOAT_AN optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'FloatTypeAnnotation' }, + returnTypeAnnotation: { type: 'FloatTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: false, type: 'FloatTypeAnnotation' } + typeAnnotation: { type: 'FloatTypeAnnotation' } } - ], - nullable: false + ] } } ] @@ -888,37 +782,27 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_NESTED_A SampleTurboModule: { aliases: { Bar: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'z', optional: false, - typeAnnotation: { nullable: false, type: 'NumberTypeAnnotation' } + typeAnnotation: { type: 'NumberTypeAnnotation' } } ] }, Foo: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'bar1', optional: false, - typeAnnotation: { - nullable: false, - type: 'TypeAliasTypeAnnotation', - name: 'Bar' - } + typeAnnotation: { type: 'TypeAliasTypeAnnotation', name: 'Bar' } }, { name: 'bar2', optional: false, - typeAnnotation: { - nullable: false, - type: 'TypeAliasTypeAnnotation', - name: 'Bar' - } + typeAnnotation: { type: 'TypeAliasTypeAnnotation', name: 'Bar' } } ] } @@ -929,23 +813,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_NESTED_A optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { - nullable: false, - type: 'TypeAliasTypeAnnotation', - name: 'Foo' - }, + returnTypeAnnotation: { type: 'TypeAliasTypeAnnotation', name: 'Foo' }, params: [ { name: 'x', optional: false, - typeAnnotation: { - nullable: false, - type: 'TypeAliasTypeAnnotation', - name: 'Foo' - } + typeAnnotation: { type: 'TypeAliasTypeAnnotation', name: 'Foo' } } - ], - nullable: false + ] } }, { @@ -953,19 +828,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_NESTED_A optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'x', optional: false, - typeAnnotation: { - nullable: false, - type: 'TypeAliasTypeAnnotation', - name: 'Foo' - } + typeAnnotation: { type: 'TypeAliasTypeAnnotation', name: 'Foo' } } - ], - nullable: false + ] } } ] @@ -989,15 +859,17 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_NULLABLE optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'VoidTypeAnnotation' }, + returnTypeAnnotation: { type: 'VoidTypeAnnotation' }, params: [ { name: 'arg', optional: false, - typeAnnotation: { nullable: true, type: 'StringTypeAnnotation' } + typeAnnotation: { + type: 'NullableTypeAnnotation', + typeAnnotation: { type: 'StringTypeAnnotation' } + } } - ], - nullable: false + ] } } ] @@ -1016,13 +888,12 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W SampleTurboModule: { aliases: { DisplayMetricsAndroid: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'width', optional: false, - typeAnnotation: { nullable: false, type: 'NumberTypeAnnotation' } + typeAnnotation: { type: 'NumberTypeAnnotation' } } ] } @@ -1034,21 +905,18 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'Dimensions', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'windowPhysicalPixels', optional: false, typeAnnotation: { - nullable: false, type: 'TypeAliasTypeAnnotation', name: 'DisplayMetricsAndroid' } @@ -1058,8 +926,7 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W } ] }, - params: [], - nullable: false + params: [] } }, { @@ -1068,21 +935,18 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'Dimensions', optional: false, typeAnnotation: { - nullable: false, type: 'ObjectTypeAnnotation', properties: [ { name: 'windowPhysicalPixels', optional: false, typeAnnotation: { - nullable: false, type: 'TypeAliasTypeAnnotation', name: 'DisplayMetricsAndroid' } @@ -1092,8 +956,7 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W } ] }, - params: [], - nullable: false + params: [] } } ] @@ -1117,9 +980,8 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PROMISE optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'PromiseTypeAnnotation' }, - params: [], - nullable: false + returnTypeAnnotation: { type: 'PromiseTypeAnnotation' }, + params: [] } }, { @@ -1127,9 +989,8 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PROMISE optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'PromiseTypeAnnotation' }, - params: [], - nullable: false + returnTypeAnnotation: { type: 'PromiseTypeAnnotation' }, + params: [] } }, { @@ -1137,9 +998,8 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PROMISE optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { nullable: false, type: 'PromiseTypeAnnotation' }, - params: [], - nullable: false + returnTypeAnnotation: { type: 'PromiseTypeAnnotation' }, + params: [] } } ] @@ -1164,7 +1024,6 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ROOT_TAG typeAnnotation: { type: 'FunctionTypeAnnotation', returnTypeAnnotation: { - nullable: false, type: 'ReservedFunctionValueTypeAnnotation', name: 'RootTag' }, @@ -1173,13 +1032,11 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ROOT_TAG name: 'rootTag', optional: false, typeAnnotation: { - nullable: false, type: 'ReservedFunctionValueTypeAnnotation', name: 'RootTag' } } - ], - nullable: false + ] } } ] @@ -1203,21 +1060,14 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_SIMPLE_O optional: false, typeAnnotation: { type: 'FunctionTypeAnnotation', - returnTypeAnnotation: { - nullable: false, - type: 'GenericObjectTypeAnnotation' - }, + returnTypeAnnotation: { type: 'GenericObjectTypeAnnotation' }, params: [ { name: 'o', optional: false, - typeAnnotation: { - nullable: false, - type: 'GenericObjectTypeAnnotation' - } + typeAnnotation: { type: 'GenericObjectTypeAnnotation' } } - ], - nullable: false + ] } } ] diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/module-parser-e2e-test.js b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/module-parser-e2e-test.js index b48f155accf..602efd71759 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/module-parser-e2e-test.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/module-parser-e2e-test.js @@ -10,9 +10,13 @@ import type { ReservedFunctionValueTypeName, + NativeModuleReturnTypeAnnotation, + NativeModuleBaseTypeAnnotation, NativeModuleSchema, + NativeModuleParamTypeAnnotation, } from '../../../../CodegenSchema'; const {parseString} = require('../../index.js'); +const {unwrapNullable} = require('../utils'); const { FlowGenericNotTypeParameterizedParserError, UnrecognizedFlowTypeAnnotationParserError, @@ -58,14 +62,16 @@ function expectAnimalTypeAliasToExist(module: NativeModuleSchema) { expect(animalAlias).not.toBe(null); invariant(animalAlias != null, ''); expect(animalAlias.type).toBe('ObjectTypeAnnotation'); - expect(animalAlias.nullable).toBe(false); expect(animalAlias.properties.length).toBe(1); expect(animalAlias.properties[0].name).toBe('name'); expect(animalAlias.properties[0].optional).toBe(false); - expect(animalAlias.properties[0].typeAnnotation.type).toBe( - 'StringTypeAnnotation', + + const [typeAnnotation, nullable] = unwrapNullable( + animalAlias.properties[0].typeAnnotation, ); - expect(animalAlias.properties[0].typeAnnotation.nullable).toBe(false); + + expect(typeAnnotation.type).toBe('StringTypeAnnotation'); + expect(nullable).toBe(false); } describe('Flow Module Parser', () => { @@ -133,7 +139,10 @@ describe('Flow Module Parser', () => { return `${paramName}: ${paramType}`; } - function parseParamType(paramName: string, paramType: string) { + function parseParamType( + paramName: string, + paramType: string, + ): [NativeModuleParamTypeAnnotation, NativeModuleSchema] { const module = parseModule(` import type {TurboModule} from 'RCTExport'; import * as TurboModuleRegistry from 'TurboModuleRegistry'; @@ -147,13 +156,18 @@ describe('Flow Module Parser', () => { `); expect(module.properties[0]).not.toBe(null); - const param = module.properties[0].typeAnnotation.params[0]; + const param = unwrapNullable(module.properties[0].typeAnnotation)[0] + .params[0]; expect(param).not.toBe(null); expect(param.name).toBe(paramName); expect(param.optional).toBe(optional); - expect(param.typeAnnotation.nullable).toBe(nullable); + const [ + paramTypeAnnotation, + isParamTypeAnnotationNullable, + ] = unwrapNullable(param.typeAnnotation); + expect(isParamTypeAnnotationNullable).toBe(nullable); - return [param, module]; + return [paramTypeAnnotation, module]; } describe( @@ -174,15 +188,15 @@ describe('Flow Module Parser', () => { describe('Primitive types', () => { PRIMITIVES.forEach(([FLOW_TYPE, PARSED_TYPE_NAME]) => { it(`should parse methods that have ${PARAM_TYPE_DESCRIPTION} primitive parameter of type '${FLOW_TYPE}'`, () => { - const [param] = parseParamType('arg', FLOW_TYPE); - expect(param.typeAnnotation.type).toBe(PARSED_TYPE_NAME); + const [paramTypeAnnotation] = parseParamType('arg', FLOW_TYPE); + expect(paramTypeAnnotation.type).toBe(PARSED_TYPE_NAME); }); }); }); it(`should parse methods that have ${PARAM_TYPE_DESCRIPTION} parameter of type 'Object'`, () => { - const [param] = parseParamType('arg', 'Object'); - expect(param.typeAnnotation.type).toBe( + const [paramTypeAnnotation] = parseParamType('arg', 'Object'); + expect(paramTypeAnnotation.type).toBe( 'GenericObjectTypeAnnotation', ); }); @@ -190,18 +204,18 @@ describe('Flow Module Parser', () => { describe('Reserved Types', () => { RESERVED_FUNCTION_VALUE_TYPE_NAME.forEach(FLOW_TYPE => { it(`should parse methods that have ${PARAM_TYPE_DESCRIPTION} parameter of reserved type '${FLOW_TYPE}'`, () => { - const [param] = parseParamType('arg', FLOW_TYPE); + const [paramTypeAnnotation] = parseParamType('arg', FLOW_TYPE); - expect(param.typeAnnotation.type).toBe( + expect(paramTypeAnnotation.type).toBe( 'ReservedFunctionValueTypeAnnotation', ); invariant( - param.typeAnnotation.type === + paramTypeAnnotation.type === 'ReservedFunctionValueTypeAnnotation', 'Param must be a Reserved type', ); - expect(param.typeAnnotation.name).toBe(FLOW_TYPE); + expect(paramTypeAnnotation.name).toBe(FLOW_TYPE); }); }); }); @@ -219,21 +233,25 @@ describe('Flow Module Parser', () => { function parseParamArrayElementType( paramName: string, paramType: string, - ) { - const [param, module] = parseParamType( + ): [NativeModuleBaseTypeAnnotation, NativeModuleSchema] { + const [paramTypeAnnotation, module] = parseParamType( paramName, `Array<${paramType}>`, ); - expect(param.typeAnnotation.type).toBe('ArrayTypeAnnotation'); - invariant( - param.typeAnnotation.type === 'ArrayTypeAnnotation', - '', - ); + expect(paramTypeAnnotation.type).toBe('ArrayTypeAnnotation'); + invariant(paramTypeAnnotation.type === 'ArrayTypeAnnotation', ''); - expect(param.typeAnnotation.elementType).not.toBe(null); - invariant(param.typeAnnotation.elementType != null, ''); - return [param.typeAnnotation.elementType, module]; + expect(paramTypeAnnotation.elementType).not.toBe(null); + invariant(paramTypeAnnotation.elementType != null, ''); + const [ + elementType, + isElementTypeNullable, + ] = unwrapNullable( + paramTypeAnnotation.elementType, + ); + expect(isElementTypeNullable).toBe(false); + return [elementType, module]; } // TODO: Do we support nullable element types? @@ -304,35 +322,44 @@ describe('Flow Module Parser', () => { expect(properties[0]).not.toBe(null); expect(properties[0].name).toBe('foo'); expect(properties[0].typeAnnotation).not.toBe(null); - expect(properties[0].typeAnnotation?.type).toBe( - 'StringTypeAnnotation', + + const [typeAnnotation, isPropertyNullable] = unwrapNullable( + properties[0].typeAnnotation, ); - expect(properties[0].typeAnnotation?.nullable).toBe(true); + + expect(typeAnnotation.type).toBe('StringTypeAnnotation'); + expect(isPropertyNullable).toBe(true); expect(properties[0].optional).toBe(false); }); }); it(`should parse methods that have ${PARAM_TYPE_DESCRIPTION} parameter type of some type alias`, () => { - const [param, module] = parseParamType('arg', 'Animal'); - expect(param.typeAnnotation.type).toBe('TypeAliasTypeAnnotation'); + const [paramTypeAnnotation, module] = parseParamType( + 'arg', + 'Animal', + ); + expect(paramTypeAnnotation.type).toBe('TypeAliasTypeAnnotation'); invariant( - param.typeAnnotation.type === 'TypeAliasTypeAnnotation', + paramTypeAnnotation.type === 'TypeAliasTypeAnnotation', '', ); - expect(param.typeAnnotation.name).toBe('Animal'); + expect(paramTypeAnnotation.name).toBe('Animal'); expectAnimalTypeAliasToExist(module); }); it(`should parse methods that have ${PARAM_TYPE_DESCRIPTION} parameter type of some type alias that points to another type alias`, () => { - const [param, module] = parseParamType('arg', 'AnimalPointer'); - expect(param.typeAnnotation.type).toBe('TypeAliasTypeAnnotation'); + const [paramTypeAnnotation, module] = parseParamType( + 'arg', + 'AnimalPointer', + ); + expect(paramTypeAnnotation.type).toBe('TypeAliasTypeAnnotation'); invariant( - param.typeAnnotation.type === 'TypeAliasTypeAnnotation', + paramTypeAnnotation.type === 'TypeAliasTypeAnnotation', '', ); - expect(param.typeAnnotation.name).toBe('Animal'); + expect(paramTypeAnnotation.name).toBe('Animal'); expectAnimalTypeAliasToExist(module); }); @@ -354,18 +381,23 @@ describe('Flow Module Parser', () => { `); expect(module.properties[0]).not.toBe(null); - const param = module.properties[0].typeAnnotation.params[0]; + const param = unwrapNullable(module.properties[0].typeAnnotation)[0] + .params[0]; expect(param.name).toBe('arg'); expect(param.optional).toBe(optional); // The TypeAliasAnnotation is called Animal, and is nullable - expect(param.typeAnnotation.type).toBe('TypeAliasTypeAnnotation'); + const [ + paramTypeAnnotation, + isParamTypeAnnotationNullable, + ] = unwrapNullable(param.typeAnnotation); + expect(paramTypeAnnotation.type).toBe('TypeAliasTypeAnnotation'); invariant( - param.typeAnnotation.type === 'TypeAliasTypeAnnotation', + paramTypeAnnotation.type === 'TypeAliasTypeAnnotation', '', ); - expect(param.typeAnnotation.name).toBe('Animal'); - expect(param.typeAnnotation.nullable).toBe(true); + expect(paramTypeAnnotation.name).toBe('Animal'); + expect(isParamTypeAnnotationNullable).toBe(true); // The Animal type alias RHS is valid, and non-null expectAnimalTypeAliasToExist(module); @@ -402,19 +434,26 @@ describe('Flow Module Parser', () => { function parseParamTypeObjectLiteralProp( propName: string, propType: string, - ) { - const [param, module] = parseParamType( + ): [ + $ReadOnly<{ + name: string, + optional: boolean, + typeAnnotation: NativeModuleBaseTypeAnnotation, + }>, + NativeModuleSchema, + ] { + const [paramTypeAnnotation, module] = parseParamType( 'arg', `{|${annotateProp(propName, propType)}|}`, ); - expect(param.typeAnnotation.type).toBe('ObjectTypeAnnotation'); + expect(paramTypeAnnotation.type).toBe('ObjectTypeAnnotation'); invariant( - param.typeAnnotation.type === 'ObjectTypeAnnotation', + paramTypeAnnotation.type === 'ObjectTypeAnnotation', '', ); - const {properties} = param.typeAnnotation; + const {properties} = paramTypeAnnotation; expect(properties).not.toBe(null); invariant(properties != null, ''); @@ -422,16 +461,19 @@ describe('Flow Module Parser', () => { expect(properties.length).toBe(1); expect(properties[0].name).toBe(propName); expect(properties[0].optional).toBe(isPropOptional); - expect(properties[0].typeAnnotation).not.toBe(null); - expect(properties[0].typeAnnotation.nullable).toBe( - isPropNullable, - ); - invariant(properties[0].typeAnnotation != null, ''); + + const [ + propertyTypeAnnotation, + isPropertyTypeAnnotationNullable, + ] = unwrapNullable(properties[0].typeAnnotation); + + expect(propertyTypeAnnotation).not.toBe(null); + expect(isPropertyTypeAnnotationNullable).toBe(isPropNullable); return [ { ...properties[0], - typeAnnotation: properties[0].typeAnnotation, + typeAnnotation: propertyTypeAnnotation, }, module, ]; @@ -504,7 +546,7 @@ describe('Flow Module Parser', () => { function parseArrayElementType( propName: string, arrayElementType: string, - ) { + ): [NativeModuleBaseTypeAnnotation, NativeModuleSchema] { const [property, module] = parseParamTypeObjectLiteralProp( 'propName', `Array<${arrayElementType}>`, @@ -517,9 +559,21 @@ describe('Flow Module Parser', () => { '', ); - const {elementType} = property.typeAnnotation; - expect(elementType).not.toBe(null); - invariant(elementType != null, ''); + const { + elementType: nullableElementType, + } = property.typeAnnotation; + expect(nullableElementType).not.toBe(null); + invariant(nullableElementType != null, ''); + + const [ + elementType, + isElementTypeNullable, + ] = unwrapNullable( + nullableElementType, + ); + + expect(isElementTypeNullable).toBe(false); + return [elementType, module]; } @@ -595,10 +649,16 @@ describe('Flow Module Parser', () => { expect(properties[0]).not.toBe(null); expect(properties[0].name).toBe('foo'); expect(properties[0].typeAnnotation).not.toBe(null); - expect(properties[0].typeAnnotation?.type).toBe( + + const [ + propertyTypeAnnotation, + isPropertyTypeAnnotationNullable, + ] = unwrapNullable(properties[0].typeAnnotation); + + expect(propertyTypeAnnotation.type).toBe( 'StringTypeAnnotation', ); - expect(properties[0].typeAnnotation?.nullable).toBe(true); + expect(isPropertyTypeAnnotationNullable).toBe(true); expect(properties[0].optional).toBe(false); }); }); @@ -623,11 +683,16 @@ describe('Flow Module Parser', () => { expect(properties[0]).not.toBe(null); expect(properties[0].name).toBe('foo'); - expect(properties[0].typeAnnotation).not.toBe(null); - expect(properties[0].typeAnnotation?.type).toBe( + + const [ + propertyTypeAnnotation, + isPropertyTypeAnnotationNullable, + ] = unwrapNullable(properties[0].typeAnnotation); + + expect(propertyTypeAnnotation.type).toBe( 'StringTypeAnnotation', ); - expect(properties[0].typeAnnotation?.nullable).toBe(true); + expect(isPropertyTypeAnnotationNullable).toBe(true); expect(properties[0].optional).toBe(false); }); @@ -668,10 +733,19 @@ describe('Flow Module Parser', () => { `); expect(module.properties[0]).not.toBe(null); - const {returnTypeAnnotation} = module.properties[0].typeAnnotation; - expect(returnTypeAnnotation).not.toBe(null); + + const [ + functionTypeAnnotation, + isFunctionTypeAnnotationNullable, + ] = unwrapNullable(module.properties[0].typeAnnotation); + expect(isFunctionTypeAnnotationNullable).toBe(false); + + const [ + returnTypeAnnotation, + isReturnTypeAnnotationNullable, + ] = unwrapNullable(functionTypeAnnotation.returnTypeAnnotation); expect(returnTypeAnnotation.type).toBe('VoidTypeAnnotation'); - expect(returnTypeAnnotation.nullable).toBe(false); + expect(isReturnTypeAnnotationNullable).toBe(false); }); [true, false].forEach(IS_RETURN_TYPE_NULLABLE => { @@ -681,7 +755,9 @@ describe('Flow Module Parser', () => { const annotateRet = retType => IS_RETURN_TYPE_NULLABLE ? `?${retType}` : retType; - function parseReturnType(flowType: string) { + function parseReturnType( + flowType: string, + ): [NativeModuleReturnTypeAnnotation, NativeModuleSchema] { const module = parseModule(` import type {TurboModule} from 'RCTExport'; import * as TurboModuleRegistry from 'TurboModuleRegistry'; @@ -695,9 +771,18 @@ describe('Flow Module Parser', () => { `); expect(module.properties[0]).not.toBe(null); - const {returnTypeAnnotation} = module.properties[0].typeAnnotation; - expect(returnTypeAnnotation).not.toBe(null); - expect(returnTypeAnnotation.nullable).toBe(IS_RETURN_TYPE_NULLABLE); + const [ + functionTypeAnnotation, + isFunctionTypeAnnotationNullable, + ] = unwrapNullable(module.properties[0].typeAnnotation); + expect(isFunctionTypeAnnotationNullable).toBe(false); + + const [ + returnTypeAnnotation, + isReturnTypeAnnotationNullable, + ] = unwrapNullable(functionTypeAnnotation.returnTypeAnnotation); + expect(isReturnTypeAnnotationNullable).toBe(IS_RETURN_TYPE_NULLABLE); + return [returnTypeAnnotation, module]; } @@ -749,7 +834,9 @@ describe('Flow Module Parser', () => { ); }); - function parseArrayElementReturnType(flowType: string) { + function parseArrayElementReturnType( + flowType: string, + ): [NativeModuleBaseTypeAnnotation, NativeModuleSchema] { const [returnTypeAnnotation, module] = parseReturnType( 'Array' + (flowType != null ? `<${flowType}>` : ''), ); @@ -759,10 +846,19 @@ describe('Flow Module Parser', () => { '', ); - const {elementType} = returnTypeAnnotation; + const arrayTypeAnnotation = returnTypeAnnotation; + + const {elementType} = arrayTypeAnnotation; expect(elementType).not.toBe(null); invariant(elementType != null, ''); - return [elementType, module]; + + const [ + elementTypeAnnotation, + isElementTypeAnnotation, + ] = unwrapNullable(elementType); + expect(isElementTypeAnnotation).toBe(false); + + return [elementTypeAnnotation, module]; } // TODO: Do we support nullable element types? @@ -824,10 +920,14 @@ describe('Flow Module Parser', () => { expect(properties[0]).not.toBe(null); expect(properties[0].name).toBe('foo'); expect(properties[0].typeAnnotation).not.toBe(null); - expect(properties[0].typeAnnotation?.type).toBe( - 'StringTypeAnnotation', - ); - expect(properties[0].typeAnnotation?.nullable).toBe(true); + + const [ + propertyTypeAnnotation, + isPropertyTypeAnnotationNullable, + ] = unwrapNullable(properties[0].typeAnnotation); + + expect(propertyTypeAnnotation.type).toBe('StringTypeAnnotation'); + expect(isPropertyTypeAnnotationNullable).toBe(true); expect(properties[0].optional).toBe(false); }); }); @@ -903,7 +1003,14 @@ describe('Flow Module Parser', () => { function parseObjectLiteralReturnTypeProp( propName: string, propType: string, - ) { + ): [ + $ReadOnly<{ + name: string, + optional: boolean, + typeAnnotation: NativeModuleBaseTypeAnnotation, + }>, + NativeModuleSchema, + ] { const [returnTypeAnnotation, module] = parseReturnType( `{|${annotateProp(propName, propType)}|}`, ); @@ -923,13 +1030,18 @@ describe('Flow Module Parser', () => { const property = properties[0]; expect(property.name).toBe(propName); expect(property.optional).toBe(optional); - expect(property.typeAnnotation).not.toBe(null); - expect(property.typeAnnotation?.nullable).toBe(nullable); - invariant(property.typeAnnotation != null, ''); + + const [ + propertyTypeAnnotation, + isPropertyTypeAnnotationNullable, + ] = unwrapNullable(property.typeAnnotation); + + expect(propertyTypeAnnotation).not.toBe(null); + expect(isPropertyTypeAnnotationNullable).toBe(nullable); return [ { ...property, - typeAnnotation: property.typeAnnotation, + typeAnnotation: propertyTypeAnnotation, }, module, ]; @@ -1010,7 +1122,7 @@ describe('Flow Module Parser', () => { function parseArrayElementType( propName: string, arrayElementType: string, - ) { + ): [NativeModuleBaseTypeAnnotation, NativeModuleSchema] { const [ property, module, @@ -1027,9 +1139,20 @@ describe('Flow Module Parser', () => { '', ); - const {elementType} = property.typeAnnotation; - expect(elementType).not.toBe(null); - invariant(elementType != null, ''); + const { + elementType: nullableElementType, + } = property.typeAnnotation; + expect(nullableElementType).not.toBe(null); + invariant(nullableElementType != null, ''); + + const [ + elementType, + isElementTypeNullable, + ] = unwrapNullable( + nullableElementType, + ); + expect(isElementTypeNullable).toBe(false); + return [elementType, module]; } @@ -1105,13 +1228,15 @@ describe('Flow Module Parser', () => { expect(properties[0].name).toBe('foo'); expect(properties[0].optional).toBe(false); - expect(properties[0].typeAnnotation).not.toBe(null); - invariant(properties[0].typeAnnotation != null, ''); + const [ + propertyTypeAnnotation, + isPropertyTypeAnnotationNullable, + ] = unwrapNullable(properties[0].typeAnnotation); - expect(properties[0].typeAnnotation.type).toBe( + expect(propertyTypeAnnotation.type).toBe( 'StringTypeAnnotation', ); - expect(properties[0].typeAnnotation.nullable).toBe(true); + expect(isPropertyTypeAnnotationNullable).toBe(true); }); }); @@ -1138,13 +1263,15 @@ describe('Flow Module Parser', () => { expect(properties[0].name).toBe('foo'); expect(properties[0].optional).toBe(false); - expect(properties[0].typeAnnotation).not.toBe(null); - invariant(properties[0].typeAnnotation != null, ''); + const [ + propertyTypeAnnotation, + isPropertyTypeAnnotationNullable, + ] = unwrapNullable(properties[0].typeAnnotation); - expect(properties[0].typeAnnotation.type).toBe( + expect(propertyTypeAnnotation.type).toBe( 'StringTypeAnnotation', ); - expect(properties[0].typeAnnotation.nullable).toBe(true); + expect(isPropertyTypeAnnotationNullable).toBe(true); }); it(`should parse methods that have ${RETURN_TYPE_DESCRIPTION} return type of an object literal with ${PROP_TYPE_DESCRIPTION} prop of some type alias`, () => { 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 a1da76184fb..8f936530a79 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -16,10 +16,14 @@ import type { NativeModuleAliasMap, NativeModuleSchema, NativeModuleFunctionTypeAnnotation, + NativeModuleArrayTypeAnnotation, + NativeModuleBaseTypeAnnotation, + Nullable, } from '../../../CodegenSchema.js'; import type {TypeDeclarationMap} from '../utils.js'; const {resolveTypeAnnotation} = require('../utils.js'); +const {unwrapNullable, wrapNullable} = require('./utils'); const { FlowGenericNotTypeParameterizedParserError, FlowGenericTypeParameterCountMismatchParserError, @@ -39,7 +43,7 @@ function translateTypeAnnotation( flowTypeAnnotation: $FlowFixMe, types: TypeDeclarationMap, aliasMap: {...NativeModuleAliasMap}, -): NativeModuleTypeAnnotation { +): Nullable { const { nullable, typeAnnotation, @@ -49,21 +53,21 @@ function translateTypeAnnotation( switch (typeAnnotation.type) { case 'GenericTypeAnnotation': { switch (typeAnnotation.id.name) { - case 'RootTag': - return { - nullable, + case 'RootTag': { + return wrapNullable(nullable, { type: 'ReservedFunctionValueTypeAnnotation', name: 'RootTag', - }; + }); + } case 'Promise': { assertGenericTypeAnnotationHasExactlyOneTypeParameter( moduleName, typeAnnotation, ); - return { - nullable, + + return wrapNullable(nullable, { type: 'PromiseTypeAnnotation', - }; + }); } case 'Array': case '$ReadOnlyArray': { @@ -78,11 +82,14 @@ function translateTypeAnnotation( * invalid Array ElementTypes. Then, make the elementType a required * parameter. */ - const elementType = translateTypeAnnotation( - moduleName, - typeAnnotation.typeParameters.params[0], - types, - aliasMap, + + const [elementType, isElementTypeNullable] = unwrapNullable( + translateTypeAnnotation( + moduleName, + typeAnnotation.typeParameters.params[0], + types, + aliasMap, + ), ); invariant( @@ -99,16 +106,18 @@ function translateTypeAnnotation( `${typeAnnotation.id.name} element type cannot be a function.`, ); - return { - nullable, + const finalTypeAnnotation: NativeModuleArrayTypeAnnotation< + Nullable, + > = { type: 'ArrayTypeAnnotation', - elementType: elementType, + elementType: wrapNullable(isElementTypeNullable, elementType), }; + + return wrapNullable(nullable, finalTypeAnnotation); } catch (ex) { - return { - nullable, + return wrapNullable(nullable, { type: 'ArrayTypeAnnotation', - }; + }); } } case '$ReadOnly': { @@ -124,34 +133,29 @@ function translateTypeAnnotation( ); } case 'Stringish': { - return { - nullable, + return wrapNullable(nullable, { type: 'StringTypeAnnotation', - }; + }); } case 'Int32': { - return { - nullable, + return wrapNullable(nullable, { type: 'Int32TypeAnnotation', - }; + }); } case 'Double': { - return { - nullable, + return wrapNullable(nullable, { type: 'DoubleTypeAnnotation', - }; + }); } case 'Float': { - return { - nullable, + return wrapNullable(nullable, { type: 'FloatTypeAnnotation', - }; + }); } case 'Object': { - return { - nullable, + return wrapNullable(nullable, { type: 'GenericObjectTypeAnnotation', - }; + }); } default: { throw new UnrecognizedFlowGenericParserError( @@ -162,7 +166,7 @@ function translateTypeAnnotation( } } case 'ObjectTypeAnnotation': { - const objectTypeAnnotationPartial = { + const objectTypeAnnotation = { type: 'ObjectTypeAnnotation', properties: typeAnnotation.properties.map(property => { const {optional} = property; @@ -180,19 +184,13 @@ function translateTypeAnnotation( }; if (!typeAliasResolutionStatus.successful) { - return { - nullable, - ...objectTypeAnnotationPartial, - }; + return wrapNullable(nullable, objectTypeAnnotation); } /** * All aliases RHS are required. */ - aliasMap[typeAliasResolutionStatus.aliasName] = { - nullable: false, - ...objectTypeAnnotationPartial, - }; + aliasMap[typeAliasResolutionStatus.aliasName] = objectTypeAnnotation; /** * Nullability of type aliases is transitive. @@ -223,43 +221,40 @@ function translateTypeAnnotation( * Hence, it's better to manage nullability within the actual TypeAliasTypeAnnotation nodes, and not the * associated ObjectTypeAnnotations. */ - return { - nullable: nullable, + return wrapNullable(nullable, { type: 'TypeAliasTypeAnnotation', name: typeAliasResolutionStatus.aliasName, - }; + }); } case 'BooleanTypeAnnotation': { - return { - nullable, + return wrapNullable(nullable, { type: 'BooleanTypeAnnotation', - }; + }); } case 'NumberTypeAnnotation': { - return { - nullable, + return wrapNullable(nullable, { type: 'NumberTypeAnnotation', - }; + }); } case 'VoidTypeAnnotation': { - return { - nullable, + return wrapNullable(nullable, { type: 'VoidTypeAnnotation', - }; + }); } case 'StringTypeAnnotation': { - return { - nullable, + return wrapNullable(nullable, { type: 'StringTypeAnnotation', - }; + }); } case 'FunctionTypeAnnotation': { - return translateFunctionTypeAnnotation( - moduleName, - typeAnnotation, - types, + return wrapNullable( nullable, - aliasMap, + translateFunctionTypeAnnotation( + moduleName, + typeAnnotation, + types, + aliasMap, + ), ); } default: { @@ -305,7 +300,6 @@ function translateFunctionTypeAnnotation( // TODO(T71778680): This is a FunctionTypeAnnotation. Type this. flowFunctionTypeAnnotation: $FlowFixMe, types: TypeDeclarationMap, - nullable: boolean, aliasMap: {...NativeModuleAliasMap}, ): NativeModuleFunctionTypeAnnotation { const params: Array = []; @@ -315,11 +309,13 @@ function translateFunctionTypeAnnotation( } const paramName = flowParam.name.name; - const paramTypeAnnotation = translateTypeAnnotation( - moduleName, - flowParam.typeAnnotation, - types, - aliasMap, + const [paramTypeAnnotation, isParamTypeAnnotationNullable] = unwrapNullable( + translateTypeAnnotation( + moduleName, + flowParam.typeAnnotation, + types, + aliasMap, + ), ); invariant( @@ -335,15 +331,20 @@ function translateFunctionTypeAnnotation( params.push({ name: flowParam.name.name, optional: flowParam.optional, - typeAnnotation: paramTypeAnnotation, + typeAnnotation: wrapNullable( + isParamTypeAnnotationNullable, + paramTypeAnnotation, + ), }); } - const returnTypeAnnotation = translateTypeAnnotation( - moduleName, - flowFunctionTypeAnnotation.returnType, - types, - aliasMap, + const [returnTypeAnnotation, isReturnTypeAnnotationNullable] = unwrapNullable( + translateTypeAnnotation( + moduleName, + flowFunctionTypeAnnotation.returnType, + types, + aliasMap, + ), ); invariant( @@ -353,9 +354,11 @@ function translateFunctionTypeAnnotation( return { type: 'FunctionTypeAnnotation', - returnTypeAnnotation, + returnTypeAnnotation: wrapNullable( + isReturnTypeAnnotationNullable, + returnTypeAnnotation, + ), params, - nullable, }; } @@ -385,12 +388,9 @@ function buildPropertySchema( return { name: methodName, optional: property.optional, - typeAnnotation: translateFunctionTypeAnnotation( - moduleName, - value, - types, + typeAnnotation: wrapNullable( nullable, - aliasMap, + translateFunctionTypeAnnotation(moduleName, value, types, aliasMap), ), }; } diff --git a/packages/react-native-codegen/src/parsers/flow/modules/utils.js b/packages/react-native-codegen/src/parsers/flow/modules/utils.js new file mode 100644 index 00000000000..8b8aa0b0648 --- /dev/null +++ b/packages/react-native-codegen/src/parsers/flow/modules/utils.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +import type { + NativeModuleTypeAnnotation, + Nullable, +} from '../../../CodegenSchema.js'; + +function unwrapNullable<+T: NativeModuleTypeAnnotation>( + x: Nullable, +): [T, boolean] { + if (x.type === 'NullableTypeAnnotation') { + return [x.typeAnnotation, true]; + } + + return [x, false]; +} + +function wrapNullable<+T: NativeModuleTypeAnnotation>( + nullable: boolean, + typeAnnotation: T, +): Nullable { + if (!nullable) { + return typeAnnotation; + } + + return { + type: 'NullableTypeAnnotation', + typeAnnotation, + }; +} + +module.exports = { + unwrapNullable, + wrapNullable, +};