diff --git a/packages/react-native-codegen/src/parsers/consistency/__tests__/checkComponentSnaps-test.js b/packages/react-native-codegen/src/parsers/consistency/__tests__/checkComponentSnaps-test.js index c1088b1f0b..b06dd17789 100644 --- a/packages/react-native-codegen/src/parsers/consistency/__tests__/checkComponentSnaps-test.js +++ b/packages/react-native-codegen/src/parsers/consistency/__tests__/checkComponentSnaps-test.js @@ -21,7 +21,7 @@ const tsExtraCases = [ 'ARRAY2_PROP_TYPES_NO_EVENTS', 'PROPS_AND_EVENTS_WITH_INTERFACES', ]; -const ignoredCases = ['ARRAY_PROP_TYPES_NO_EVENTS']; +const ignoredCases = []; compareSnaps( flowFixtures, diff --git a/packages/react-native-codegen/src/parsers/typescript/components/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/typescript/components/__test_fixtures__/fixtures.js index 4005574ba6..0b15b6cf33 100644 --- a/packages/react-native-codegen/src/parsers/typescript/components/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/typescript/components/__test_fixtures__/fixtures.js @@ -452,6 +452,13 @@ export interface ModuleProps extends ViewProps { array_of_array_of_object_required_in_file: ReadonlyArray< ReadonlyArray >; + + // Nested array of array of object types (with spread) + array_of_array_of_object_required_with_spread: ReadonlyArray< + ReadonlyArray< + Readonly, + >, + >, } export default codegenNativeComponent( @@ -584,6 +591,9 @@ export interface ModuleProps extends ViewProps { // Nested array of array of object types (in file) array_of_array_of_object_required_in_file: readonly ObjectType[][]; + + // Nested array of array of object types (with spread) + array_of_array_of_object_required_with_spread: readonly Readonly[][]; } export default codegenNativeComponent( diff --git a/packages/react-native-codegen/src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap b/packages/react-native-codegen/src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap index 90947e1af6..9a31424cba 100644 --- a/packages/react-native-codegen/src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap +++ b/packages/react-native-codegen/src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap @@ -1221,6 +1221,29 @@ exports[`RN Codegen TypeScript Parser can generate fixture ARRAY_PROP_TYPES_NO_E } } } + }, + { + 'name': 'array_of_array_of_object_required_with_spread', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'prop', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation', + 'default': null + } + } + ] + } + } + } } ], 'commands': [] @@ -1905,6 +1928,29 @@ exports[`RN Codegen TypeScript Parser can generate fixture ARRAY2_PROP_TYPES_NO_ } } } + }, + { + 'name': 'array_of_array_of_object_required_with_spread', + 'optional': false, + 'typeAnnotation': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'prop', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation', + 'default': null + } + } + ] + } + } + } } ], 'commands': [] diff --git a/packages/react-native-codegen/src/parsers/typescript/components/componentsUtils.js b/packages/react-native-codegen/src/parsers/typescript/components/componentsUtils.js index 0d84786df8..94f19b0573 100644 --- a/packages/react-native-codegen/src/parsers/typescript/components/componentsUtils.js +++ b/packages/react-native-codegen/src/parsers/typescript/components/componentsUtils.js @@ -11,7 +11,10 @@ 'use strict'; import type {ASTNode} from '../utils'; import type {NamedShape} from '../../../CodegenSchema.js'; -const {parseTopLevelType} = require('../parseTopLevelType'); +const { + parseTopLevelType, + flattenIntersectionType, +} = require('../parseTopLevelType'); import type {TypeDeclarationMap} from '../../utils'; function getProperties( @@ -163,6 +166,102 @@ function detectArrayType( return null; } +function buildObjectType( + rawProperties: Array<$FlowFixMe>, + types: TypeDeclarationMap, + buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape, +): $FlowFixMe { + const flattenedProperties = flattenProperties(rawProperties, types); + const properties = flattenedProperties + .map(prop => buildSchema(prop, types)) + .filter(Boolean); + + return { + type: 'ObjectTypeAnnotation', + properties, + }; +} + +function getCommonTypeAnnotation( + name: string, + forArray: boolean, + type: string, + typeAnnotation: $FlowFixMe, + defaultValue: $FlowFixMe | void, + types: TypeDeclarationMap, + buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape, +): $FlowFixMe { + switch (type) { + case 'TSTypeLiteral': + return buildObjectType(typeAnnotation.members, types, buildSchema); + case 'TSInterfaceDeclaration': + return buildObjectType([typeAnnotation], types, buildSchema); + case 'TSIntersectionType': + return buildObjectType( + flattenIntersectionType(typeAnnotation, types), + types, + buildSchema, + ); + case 'ImageSource': + return { + type: 'ReservedPropTypeAnnotation', + name: 'ImageSourcePrimitive', + }; + case 'ImageRequest': + return { + type: 'ReservedPropTypeAnnotation', + name: 'ImageRequestPrimitive', + }; + case 'ColorValue': + case 'ProcessedColorValue': + return { + type: 'ReservedPropTypeAnnotation', + name: 'ColorPrimitive', + }; + case 'PointValue': + return { + type: 'ReservedPropTypeAnnotation', + name: 'PointPrimitive', + }; + case 'EdgeInsetsValue': + return { + type: 'ReservedPropTypeAnnotation', + name: 'EdgeInsetsPrimitive', + }; + case 'TSUnionType': + return getUnionOfLiterals( + name, + forArray, + typeAnnotation.types, + defaultValue, + types, + ); + case 'Int32': + return { + type: 'Int32TypeAnnotation', + }; + case 'Double': + return { + type: 'DoubleTypeAnnotation', + }; + case 'Float': + return { + type: 'FloatTypeAnnotation', + }; + case 'TSBooleanKeyword': + return { + type: 'BooleanTypeAnnotation', + }; + case 'Stringish': + case 'TSStringKeyword': + return { + type: 'StringTypeAnnotation', + }; + default: + return undefined; + } +} + function getTypeAnnotationForArray( name: string, typeAnnotation: $FlowFixMe, @@ -207,91 +306,57 @@ function getTypeAnnotationForArray( extractedTypeAnnotation.typeName?.name || extractedTypeAnnotation.type; + const common = getCommonTypeAnnotation( + name, + true, + type, + extractedTypeAnnotation, + defaultValue, + types, + buildSchema, + ); + if (common) { + return common; + } + switch (type) { - case 'TSTypeLiteral': - case 'TSInterfaceDeclaration': { - const rawProperties = - type === 'TSInterfaceDeclaration' - ? [extractedTypeAnnotation] - : extractedTypeAnnotation.members; - if (rawProperties === undefined) { - throw new Error(type); - } - return { - type: 'ObjectTypeAnnotation', - properties: flattenProperties(rawProperties, types) - .map(prop => buildSchema(prop, types)) - .filter(Boolean), - }; - } case 'TSNumberKeyword': return { type: 'FloatTypeAnnotation', }; - case 'ImageSource': - return { - type: 'ReservedPropTypeAnnotation', - name: 'ImageSourcePrimitive', - }; - case 'ImageRequest': - return { - type: 'ReservedPropTypeAnnotation', - name: 'ImageRequestPrimitive', - }; - case 'ColorValue': - case 'ProcessedColorValue': - return { - type: 'ReservedPropTypeAnnotation', - name: 'ColorPrimitive', - }; - case 'PointValue': - return { - type: 'ReservedPropTypeAnnotation', - name: 'PointPrimitive', - }; - case 'EdgeInsetsValue': - return { - type: 'ReservedPropTypeAnnotation', - name: 'EdgeInsetsPrimitive', - }; - case 'Stringish': - return { - type: 'StringTypeAnnotation', - }; - case 'Int32': - return { - type: 'Int32TypeAnnotation', - }; - case 'Double': - return { - type: 'DoubleTypeAnnotation', - }; - case 'Float': - return { - type: 'FloatTypeAnnotation', - }; - case 'TSBooleanKeyword': - return { - type: 'BooleanTypeAnnotation', - }; - case 'TSStringKeyword': - return { - type: 'StringTypeAnnotation', - }; - case 'TSUnionType': - return getUnionOfLiterals( - name, - true, - extractedTypeAnnotation.types, - defaultValue, - types, - ); default: (type: empty); throw new Error(`Unknown prop type for "${name}": ${type}`); } } +function setDefaultValue( + common: $FlowFixMe, + defaultValue: $FlowFixMe | void, +): void { + switch (common.type) { + case 'Int32TypeAnnotation': + case 'DoubleTypeAnnotation': + common.default = ((defaultValue ? defaultValue : 0): number); + break; + case 'FloatTypeAnnotation': + common.default = ((defaultValue === null + ? null + : defaultValue + ? defaultValue + : 0): number | null); + break; + case 'BooleanTypeAnnotation': + common.default = defaultValue === null ? null : !!defaultValue; + break; + case 'StringTypeAnnotation': + common.default = ((defaultValue === undefined ? null : defaultValue): + | string + | null); + break; + } +} + function getTypeAnnotation( name: string, annotation: $FlowFixMe | ASTNode, @@ -319,39 +384,21 @@ function getTypeAnnotation( ? typeAnnotation.typeName.name : typeAnnotation.type; - switch (type) { - case 'TSTypeLiteral': - case 'TSInterfaceDeclaration': { - const rawProperties = - type === 'TSInterfaceDeclaration' - ? [typeAnnotation] - : typeAnnotation.members; - const flattenedProperties = flattenProperties(rawProperties, types); - const properties = flattenedProperties - .map(prop => buildSchema(prop, types)) - .filter(Boolean); + const common = getCommonTypeAnnotation( + name, + false, + type, + typeAnnotation, + defaultValue, + types, + buildSchema, + ); + if (common) { + setDefaultValue(common, defaultValue); + return common; + } - return { - type: 'ObjectTypeAnnotation', - properties, - }; - } - case 'ImageSource': - return { - type: 'ReservedPropTypeAnnotation', - name: 'ImageSourcePrimitive', - }; - case 'ImageRequest': - return { - type: 'ReservedPropTypeAnnotation', - name: 'ImageRequestPrimitive', - }; - case 'ColorValue': - case 'ProcessedColorValue': - return { - type: 'ReservedPropTypeAnnotation', - name: 'ColorPrimitive', - }; + switch (type) { case 'ColorArrayValue': return { type: 'ArrayTypeAnnotation', @@ -360,66 +407,10 @@ function getTypeAnnotation( name: 'ColorPrimitive', }, }; - case 'PointValue': - return { - type: 'ReservedPropTypeAnnotation', - name: 'PointPrimitive', - }; - case 'EdgeInsetsValue': - return { - type: 'ReservedPropTypeAnnotation', - name: 'EdgeInsetsPrimitive', - }; - case 'Int32': - return { - type: 'Int32TypeAnnotation', - default: ((defaultValue ? defaultValue : 0): number), - }; - case 'Double': - return { - type: 'DoubleTypeAnnotation', - default: ((defaultValue ? defaultValue : 0): number), - }; - case 'Float': - return { - type: 'FloatTypeAnnotation', - default: ((defaultValue === null - ? null - : defaultValue - ? defaultValue - : 0): number | null), - }; - case 'TSBooleanKeyword': - return { - type: 'BooleanTypeAnnotation', - default: defaultValue === null ? null : !!defaultValue, - }; - case 'TSStringKeyword': - return { - type: 'StringTypeAnnotation', - default: ((defaultValue === undefined ? null : defaultValue): - | string - | null), - }; - case 'Stringish': - return { - type: 'StringTypeAnnotation', - default: ((defaultValue === undefined ? null : defaultValue): - | string - | null), - }; case 'TSNumberKeyword': throw new Error( `Cannot use "${type}" type annotation for "${name}": must use a specific numeric type like Int32, Double, or Float`, ); - case 'TSUnionType': - return getUnionOfLiterals( - name, - false, - typeAnnotation.types, - defaultValue, - types, - ); default: (type: empty); throw new Error(`Unknown prop type for "${name}": "${type}"`); @@ -498,6 +489,8 @@ function flattenProperties( return flattenProperties(property.members, types); } else if (property.type === 'TSInterfaceDeclaration') { return flattenProperties(getProperties(property.id.name, types), types); + } else if (property.type === 'TSIntersectionType') { + return flattenProperties(property.types, types); } else { throw new Error( `${property.type} is not a supported object literal type.`, diff --git a/packages/react-native-codegen/src/parsers/typescript/parseTopLevelType.js b/packages/react-native-codegen/src/parsers/typescript/parseTopLevelType.js index 8ec8580e66..79b7ed30eb 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parseTopLevelType.js +++ b/packages/react-native-codegen/src/parsers/typescript/parseTopLevelType.js @@ -184,6 +184,60 @@ function parseTopLevelType( } } +function handleIntersectionAndParen( + type: $FlowFixMe, + result: Array<$FlowFixMe>, + knownTypes?: TypeDeclarationMap, +): void { + switch (type.type) { + case 'TSParenthesizedType': { + handleIntersectionAndParen(type.typeAnnotation, result, knownTypes); + break; + } + case 'TSIntersectionType': { + for (const t of type.types) { + handleIntersectionAndParen(t, result, knownTypes); + } + break; + } + case 'TSTypeReference': + if (type.typeName.name === 'Readonly') { + handleIntersectionAndParen( + type.typeParameters.params[0], + result, + knownTypes, + ); + } else if (type.typeName.name === 'WithDefault') { + throw new Error('WithDefault<> is now allowed in intersection types.'); + } else if (!knownTypes) { + result.push(type); + } else { + const resolvedType = getValueFromTypes(type, knownTypes); + if ( + resolvedType.type === 'TSTypeReference' && + resolvedType.typeName.name === type.typeName.name + ) { + result.push(type); + } else { + handleIntersectionAndParen(resolvedType, result, knownTypes); + } + } + break; + default: + result.push(type); + } +} + +function flattenIntersectionType( + type: $FlowFixMe, + knownTypes?: TypeDeclarationMap, +): Array<$FlowFixMe> { + const result: Array<$FlowFixMe> = []; + handleIntersectionAndParen(type, result, knownTypes); + return result; +} + module.exports = { parseTopLevelType, + flattenIntersectionType, };