Add intersection types in react-native-codegen for TypeScript (#35247)
Summary: Refer to "Support intersection of object types, `{...a, ...b, ...}` in Flow is equivalent to `a & b & {...}` in TypeScript, the order and the form is not important." in https://github.com/facebook/react-native/issues/34872 In this change I also extract the common part from `getTypeAnnotation` and `getTypeAnnotationForArray` into `getCommonTypeAnnotation`. Most of the differences are about `default` in the schema. After a schema is generated from `getCommonTypeAnnotation` successfully, `getTypeAnnotation` will fill `default` if necessary, while `getTypeAnnotationForArray` does nothing. ## Changelog [General] [Changed] - Add intersection types in react-native-codegen for TypeScript Pull Request resolved: https://github.com/facebook/react-native/pull/35247 Test Plan: `yarn jest react-native-codegen` passed Reviewed By: cipolleschi Differential Revision: D41105744 Pulled By: lunaleaps fbshipit-source-id: cd250a9594a54596a20ae26e57a1c801e2047703
This commit is contained in:
Родитель
63a4539e4d
Коммит
813fd04118
|
@ -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,
|
||||
|
|
|
@ -452,6 +452,13 @@ export interface ModuleProps extends ViewProps {
|
|||
array_of_array_of_object_required_in_file: ReadonlyArray<
|
||||
ReadonlyArray<ObjectType>
|
||||
>;
|
||||
|
||||
// Nested array of array of object types (with spread)
|
||||
array_of_array_of_object_required_with_spread: ReadonlyArray<
|
||||
ReadonlyArray<
|
||||
Readonly<ObjectType & {}>,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<ModuleProps>(
|
||||
|
@ -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<ObjectType & {}>[][];
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<ModuleProps>(
|
||||
|
|
|
@ -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': []
|
||||
|
|
|
@ -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<T>(
|
|||
return null;
|
||||
}
|
||||
|
||||
function buildObjectType<T>(
|
||||
rawProperties: Array<$FlowFixMe>,
|
||||
types: TypeDeclarationMap,
|
||||
buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape<T>,
|
||||
): $FlowFixMe {
|
||||
const flattenedProperties = flattenProperties(rawProperties, types);
|
||||
const properties = flattenedProperties
|
||||
.map(prop => buildSchema(prop, types))
|
||||
.filter(Boolean);
|
||||
|
||||
return {
|
||||
type: 'ObjectTypeAnnotation',
|
||||
properties,
|
||||
};
|
||||
}
|
||||
|
||||
function getCommonTypeAnnotation<T>(
|
||||
name: string,
|
||||
forArray: boolean,
|
||||
type: string,
|
||||
typeAnnotation: $FlowFixMe,
|
||||
defaultValue: $FlowFixMe | void,
|
||||
types: TypeDeclarationMap,
|
||||
buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape<T>,
|
||||
): $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<T>(
|
||||
name: string,
|
||||
typeAnnotation: $FlowFixMe,
|
||||
|
@ -207,91 +306,57 @@ function getTypeAnnotationForArray<T>(
|
|||
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<T>(
|
||||
name: string,
|
||||
annotation: $FlowFixMe | ASTNode,
|
||||
|
@ -319,39 +384,21 @@ function getTypeAnnotation<T>(
|
|||
? 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<T>(
|
|||
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.`,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче