Refactor in turbo module TypeScript codegen: process `(T)`, `T|U`, `T|undefined` and related stuff in a central place (#34814)

Summary:
`TSParenthesizedType`, `TSUnionType`, `TSNullKeyword`, `TSUndefinedKeyword`, `TSVoidKeyword` etc are repeatly processed in so many places. In this change I put them in a new file `parseTopLevelType.js`, and everyone call that file, all repeat implementation are deleted.

The `parseTopLevelType` function will look into a type consisted by the above types in any possible combination (but still very easy to do), and tell you if a type is nullable, or if there is a default value, and what is the real type with all noises removed.

Array types and union types are processed in component twice, for property of array, and property of nested arrays (`componentsUtils.js`). They are extracted into single functions.

## Changelog

[General] [Changed] - Refactor in turbo module TypeScript codegen: process `(T)`, `T|U`, `T|undefined` and related stuff in a central place

Pull Request resolved: https://github.com/facebook/react-native/pull/34814

Test Plan: `yarn jest react-native-codegen` passed

Reviewed By: yungsters, sammy-SC

Differential Revision: D40094373

fbshipit-source-id: f28e145bc4e7734be9036815ea425d820eadb8f0
This commit is contained in:
Zihan Chen (MSFT) 2022-10-07 03:13:38 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 967de03f30
Коммит 00b795642a
8 изменённых файлов: 434 добавлений и 532 удалений

Просмотреть файл

@ -15,19 +15,18 @@ import type {
CommandTypeAnnotation,
} from '../../../CodegenSchema.js';
import type {TypeDeclarationMap} from '../utils.js';
const {getValueFromTypes} = require('../utils.js');
const {parseTopLevelType} = require('../parseTopLevelType');
type EventTypeAST = Object;
function buildCommandSchema(property: EventTypeAST, types: TypeDeclarationMap) {
const name = property.key.name;
const optional = property.optional || false;
const value = getValueFromTypes(
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
types,
);
const name = property.key.name;
const optional = property.optional || topLevelType.optional;
const value = topLevelType.type;
const firstParam = value.parameters[0].typeAnnotation;
if (
@ -45,10 +44,10 @@ function buildCommandSchema(property: EventTypeAST, types: TypeDeclarationMap) {
const params = value.parameters.slice(1).map(param => {
const paramName = param.name;
const paramValue = getValueFromTypes(
const paramValue = parseTopLevelType(
param.typeAnnotation.typeAnnotation,
types,
);
).type;
const type =
paramValue.type === 'TSTypeReference'

Просмотреть файл

@ -12,7 +12,7 @@
import type {ASTNode} from '../utils';
import type {TypeDeclarationMap} from '../utils.js';
import type {NamedShape} from '../../../CodegenSchema.js';
const {getValueFromTypes} = require('../utils.js');
const {parseTopLevelType} = require('../parseTopLevelType');
function getProperties(
typeName: string,
@ -44,105 +44,160 @@ function getProperties(
}
}
function getTypeAnnotationForObjectAsArrayElement<T>(
function getUnionOfLiterals(
name: string,
typeAnnotation: $FlowFixMe,
forArray: boolean,
elementTypes: $FlowFixMe[],
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
) {
elementTypes.reduce((lastType, currType) => {
const lastFlattenedType =
lastType && lastType.type === 'TSLiteralType'
? lastType.literal.type
: lastType.type;
const currFlattenedType =
currType.type === 'TSLiteralType' ? currType.literal.type : currType.type;
if (lastFlattenedType && currFlattenedType !== lastFlattenedType) {
throw new Error(`Mixed types are not supported (see "${name}")`);
}
return currType;
});
if (defaultValue === undefined) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = elementTypes[0].type;
if (
unionType === 'TSLiteralType' &&
elementTypes[0].literal?.type === 'StringLiteral'
) {
return {
type: 'StringEnumTypeAnnotation',
default: (defaultValue: string),
options: elementTypes.map(option => option.literal.value),
};
} else if (
unionType === 'TSLiteralType' &&
elementTypes[0].literal?.type === 'NumericLiteral'
) {
if (forArray) {
throw new Error(`Arrays of int enums are not supported (see: "${name}")`);
} else {
return {
type: 'Int32EnumTypeAnnotation',
default: (defaultValue: number),
options: elementTypes.map(option => option.literal.value),
};
}
} else {
throw new Error(
`Unsupported union type for "${name}", received "${
unionType === 'TSLiteralType'
? elementTypes[0].literal?.type
: unionType
}"`,
);
}
}
function detectArrayType<T>(
name: string,
typeAnnotation: $FlowFixMe | ASTNode,
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape<T>,
): $FlowFixMe {
// for array of array of a type
// such type must be an object literal
const elementType = getTypeAnnotationForArray(
name,
typeAnnotation,
null,
types,
buildSchema,
);
if (elementType.type !== 'ObjectTypeAnnotation') {
throw new Error(
`Only array of array of object is supported for "${name}".`,
);
// Covers: readonly T[]
if (
typeAnnotation.type === 'TSTypeOperator' &&
typeAnnotation.operator === 'readonly' &&
typeAnnotation.typeAnnotation.type === 'TSArrayType'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeAnnotation.elementType,
defaultValue,
types,
buildSchema,
),
};
}
return {
type: 'ArrayTypeAnnotation',
elementType,
};
// Covers: T[]
if (typeAnnotation.type === 'TSArrayType') {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.elementType,
defaultValue,
types,
buildSchema,
),
};
}
// Covers: Array<T> and ReadonlyArray<T>
if (
typeAnnotation.type === 'TSTypeReference' &&
(typeAnnotation.typeName.name === 'ReadonlyArray' ||
typeAnnotation.typeName.name === 'Array')
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
defaultValue,
types,
buildSchema,
),
};
}
return null;
}
function getTypeAnnotationForArray<T>(
name: string,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe | null,
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape<T>,
): $FlowFixMe {
if (typeAnnotation.type === 'TSParenthesizedType') {
return getTypeAnnotationForArray(
name,
typeAnnotation.typeAnnotation,
defaultValue,
types,
buildSchema,
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(typeAnnotation, types);
if (topLevelType.defaultValue !== undefined) {
throw new Error(
'Nested optionals such as "ReadonlyArray<boolean | null | undefined>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
);
}
const extractedTypeAnnotation = getValueFromTypes(typeAnnotation, types);
if (
extractedTypeAnnotation.type === 'TSUnionType' &&
extractedTypeAnnotation.types.some(
t => t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword',
)
) {
if (topLevelType.optional) {
throw new Error(
'Nested optionals such as "ReadonlyArray<boolean | null | undefined>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
);
}
if (
extractedTypeAnnotation.type === 'TSTypeReference' &&
extractedTypeAnnotation.typeName.name === 'WithDefault'
) {
throw new Error(
'Nested defaults such as "ReadonlyArray<WithDefault<boolean, false>>" are not supported, please declare defaults at the top level of value definitions as in "WithDefault<ReadonlyArray<boolean>, false>"',
);
}
// Covers: T[]
if (typeAnnotation.type === 'TSArrayType') {
return getTypeAnnotationForObjectAsArrayElement(
name,
typeAnnotation.elementType,
types,
buildSchema,
);
}
if (extractedTypeAnnotation.type === 'TSTypeReference') {
// Resolve the type alias if it's not defined inline
const objectType = getValueFromTypes(extractedTypeAnnotation, types);
if (objectType.typeName.name === 'Readonly') {
return getTypeAnnotationForArray(
name,
objectType.typeParameters.params[0],
defaultValue,
types,
buildSchema,
);
}
// Covers: ReadonlyArray<T>
if (objectType.typeName.name === 'ReadonlyArray') {
return getTypeAnnotationForObjectAsArrayElement(
name,
objectType.typeParameters.params[0],
types,
buildSchema,
const extractedTypeAnnotation = topLevelType.type;
const arrayType = detectArrayType(
name,
extractedTypeAnnotation,
defaultValue,
types,
buildSchema,
);
if (arrayType) {
if (arrayType.elementType.type !== 'ObjectTypeAnnotation') {
throw new Error(
`Only array of array of object is supported for "${name}".`,
);
}
return arrayType;
}
const type =
@ -157,8 +212,11 @@ function getTypeAnnotationForArray<T>(
case 'TSInterfaceDeclaration': {
const rawProperties =
type === 'TSInterfaceDeclaration'
? [typeAnnotation]
: typeAnnotation.members;
? [extractedTypeAnnotation]
: extractedTypeAnnotation.members;
if (rawProperties === undefined) {
throw new Error(type);
}
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(rawProperties, types)
@ -216,52 +274,13 @@ function getTypeAnnotationForArray<T>(
type: 'StringTypeAnnotation',
};
case 'TSUnionType':
typeAnnotation.types.reduce((lastType, currType) => {
const lastFlattenedType =
lastType && lastType.type === 'TSLiteralType'
? lastType.literal.type
: lastType.type;
const currFlattenedType =
currType.type === 'TSLiteralType'
? currType.literal.type
: currType.type;
if (lastFlattenedType && currFlattenedType !== lastFlattenedType) {
throw new Error(`Mixed types are not supported (see "${name}")`);
}
return currType;
});
if (defaultValue === null) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = typeAnnotation.types[0].type;
if (
unionType === 'TSLiteralType' &&
typeAnnotation.types[0].literal?.type === 'StringLiteral'
) {
return {
type: 'StringEnumTypeAnnotation',
default: (defaultValue: string),
options: typeAnnotation.types.map(option => option.literal.value),
};
} else if (
unionType === 'TSLiteralType' &&
typeAnnotation.types[0].literal?.type === 'NumericLiteral'
) {
throw new Error(
`Arrays of int enums are not supported (see: "${name}")`,
);
} else {
throw new Error(
`Unsupported union type for "${name}", received "${
unionType === 'TSLiteralType'
? typeAnnotation.types[0].literal?.type
: unionType
}"`,
);
}
return getUnionOfLiterals(
name,
true,
extractedTypeAnnotation.types,
defaultValue,
types,
);
default:
(type: empty);
throw new Error(`Unknown prop type for "${name}": ${type}`);
@ -271,101 +290,22 @@ function getTypeAnnotationForArray<T>(
function getTypeAnnotation<T>(
name: string,
annotation: $FlowFixMe | ASTNode,
defaultValue: $FlowFixMe | null,
withNullDefault: boolean,
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape<T>,
): $FlowFixMe {
const typeAnnotation = getValueFromTypes(annotation, types);
// Covers: (T)
if (typeAnnotation.type === 'TSParenthesizedType') {
return getTypeAnnotation(
name,
typeAnnotation.typeAnnotation,
defaultValue,
withNullDefault,
types,
buildSchema,
);
}
// Covers: readonly T[]
if (
typeAnnotation.type === 'TSTypeOperator' &&
typeAnnotation.operator === 'readonly' &&
typeAnnotation.typeAnnotation.type === 'TSArrayType'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeAnnotation.elementType,
defaultValue,
types,
buildSchema,
),
};
}
// Covers: ReadonlyArray<T>
if (
typeAnnotation.type === 'TSTypeReference' &&
typeAnnotation.typeName.name === 'ReadonlyArray'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
defaultValue,
types,
buildSchema,
),
};
}
// Covers: Readonly<T[]>
if (
typeAnnotation.type === 'TSTypeReference' &&
typeAnnotation.typeName?.name === 'Readonly' &&
typeAnnotation.typeParameters.type === 'TSTypeParameterInstantiation' &&
typeAnnotation.typeParameters.params[0].type === 'TSArrayType'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
defaultValue,
types,
buildSchema,
),
};
}
// Covers: Readonly<T>, Readonly<{ ... }>, Readonly<T | U ...>
if (
typeAnnotation.type === 'TSTypeReference' &&
typeAnnotation.typeName?.name === 'Readonly' &&
typeAnnotation.typeParameters.type === 'TSTypeParameterInstantiation'
) {
// TODO:
// the original implementation assume Readonly<TSUnionType>
// to be Readonly<{ ... } | null | undefined>
// without actually verifying it
let elementType = typeAnnotation.typeParameters.params[0];
if (elementType.type === 'TSUnionType') {
elementType = elementType.types[0];
}
return getTypeAnnotation(
name,
elementType,
defaultValue,
withNullDefault,
types,
buildSchema,
);
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(annotation, types);
const typeAnnotation = topLevelType.type;
const arrayType = detectArrayType(
name,
typeAnnotation,
defaultValue,
types,
buildSchema,
);
if (arrayType) {
return arrayType;
}
const type =
@ -433,140 +373,67 @@ function getTypeAnnotation<T>(
case 'Float':
return {
type: 'FloatTypeAnnotation',
default: withNullDefault
? (defaultValue: number | null)
: ((defaultValue ? defaultValue : 0): number),
default: ((defaultValue === null
? null
: defaultValue
? defaultValue
: 0): number | null),
};
case 'TSBooleanKeyword':
return {
type: 'BooleanTypeAnnotation',
default: withNullDefault
? (defaultValue: boolean | null)
: ((defaultValue == null ? false : defaultValue): boolean),
default: defaultValue === null ? null : !!defaultValue,
};
case 'TSStringKeyword':
if (typeof defaultValue !== 'undefined') {
return {
type: 'StringTypeAnnotation',
default: (defaultValue: string | null),
};
}
throw new Error(`A default string (or null) is required for "${name}"`);
return {
type: 'StringTypeAnnotation',
default: ((defaultValue === undefined ? null : defaultValue):
| string
| null),
};
case 'Stringish':
if (typeof defaultValue !== 'undefined') {
return {
type: 'StringTypeAnnotation',
default: (defaultValue: string | null),
};
}
throw new Error(`A default string (or null) is required for "${name}"`);
case 'TSUnionType':
typeAnnotation.types.reduce((lastType, currType) => {
const lastFlattenedType =
lastType && lastType.type === 'TSLiteralType'
? lastType.literal.type
: lastType.type;
const currFlattenedType =
currType.type === 'TSLiteralType'
? currType.literal.type
: currType.type;
if (lastFlattenedType && currFlattenedType !== lastFlattenedType) {
throw new Error(`Mixed types are not supported (see "${name}")`);
}
return currType;
});
if (defaultValue === null) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = typeAnnotation.types[0].type;
if (
unionType === 'TSLiteralType' &&
typeAnnotation.types[0].literal?.type === 'StringLiteral'
) {
return {
type: 'StringEnumTypeAnnotation',
default: (defaultValue: string),
options: typeAnnotation.types.map(option => option.literal.value),
};
} else if (
unionType === 'TSLiteralType' &&
typeAnnotation.types[0].literal?.type === 'NumericLiteral'
) {
return {
type: 'Int32EnumTypeAnnotation',
default: (defaultValue: number),
options: typeAnnotation.types.map(option => option.literal.value),
};
} else {
throw new Error(
`Unsupported union type for "${name}", received "${
unionType === 'TSLiteralType'
? typeAnnotation.types[0].literal?.type
: unionType
}"`,
);
}
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}"`);
}
}
function findProp(
name: string,
typeAnnotation: $FlowFixMe,
optionalType: boolean,
) {
switch (typeAnnotation.type) {
// Check for (T)
case 'TSParenthesizedType':
return findProp(name, typeAnnotation.typeAnnotation, optionalType);
// Check for optional type in union e.g. T | null | undefined
case 'TSUnionType':
return findProp(
name,
typeAnnotation.types.filter(
t => t.type !== 'TSNullKeyword' && t.type !== 'TSUndefinedKeyword',
)[0],
optionalType ||
typeAnnotation.types.some(
t => t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword',
),
);
case 'TSTypeReference':
// Check against optional type inside `WithDefault`
if (typeAnnotation.typeName.name === 'WithDefault' && optionalType) {
throw new Error(
'WithDefault<> is optional and does not need to be marked as optional. Please remove the union of undefined and/or null',
);
}
// Remove unwanted types
if (
typeAnnotation.typeName.name === 'DirectEventHandler' ||
typeAnnotation.typeName.name === 'BubblingEventHandler'
) {
return null;
}
if (
name === 'style' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
typeAnnotation.typeName.name === 'ViewStyleProp'
) {
return null;
}
return {typeAnnotation, optionalType};
default:
return {typeAnnotation, optionalType};
function isProp(name: string, typeAnnotation: $FlowFixMe) {
if (typeAnnotation.type === 'TSTypeReference') {
// Remove unwanted types
if (
typeAnnotation.typeName.name === 'DirectEventHandler' ||
typeAnnotation.typeName.name === 'BubblingEventHandler'
) {
return false;
}
if (
name === 'style' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
typeAnnotation.typeName.name === 'ViewStyleProp'
) {
return false;
}
}
return true;
}
type SchemaInfo = {
@ -574,98 +441,34 @@ type SchemaInfo = {
optional: boolean,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe,
withNullDefault: boolean,
};
function getSchemaInfo(
property: PropAST,
types: TypeDeclarationMap,
): ?SchemaInfo {
const name = property.key.name;
const value = getValueFromTypes(
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
types,
);
const foundProp = findProp(name, value, false);
if (!foundProp) {
const name = property.key.name;
if (!isProp(name, topLevelType.type)) {
return null;
}
let {typeAnnotation, optionalType} = foundProp;
let optional = property.optional || optionalType;
// example: Readonly<{prop: string} | null | undefined>;
if (
value.type === 'TSTypeReference' &&
typeAnnotation.typeParameters?.params[0].type === 'TSUnionType' &&
typeAnnotation.typeParameters?.params[0].types.some(
element =>
element.type === 'TSNullKeyword' ||
element.type === 'TSUndefinedKeyword',
)
) {
optional = true;
}
if (
!property.optional &&
value.type === 'TSTypeReference' &&
typeAnnotation.typeName.name === 'WithDefault'
) {
if (!property.optional && topLevelType.defaultValue !== undefined) {
throw new Error(
`key ${name} must be optional if used with WithDefault<> annotation`,
);
}
let type = typeAnnotation.type;
let defaultValue = null;
let withNullDefault = false;
if (
type === 'TSTypeReference' &&
typeAnnotation.typeName.name === 'WithDefault'
) {
if (typeAnnotation.typeParameters.params.length === 1) {
throw new Error(
`WithDefault requires two parameters, did you forget to provide a default value for "${name}"?`,
);
}
let defaultValueType = typeAnnotation.typeParameters.params[1].type;
defaultValue = typeAnnotation.typeParameters.params[1].value;
if (defaultValueType === 'TSLiteralType') {
defaultValueType = typeAnnotation.typeParameters.params[1].literal.type;
defaultValue = typeAnnotation.typeParameters.params[1].literal.value;
if (
defaultValueType === 'UnaryExpression' &&
typeAnnotation.typeParameters.params[1].literal.argument.type ===
'NumericLiteral' &&
typeAnnotation.typeParameters.params[1].literal.operator === '-'
) {
defaultValue =
-1 * typeAnnotation.typeParameters.params[1].literal.argument.value;
}
}
if (defaultValueType === 'TSNullKeyword') {
defaultValue = null;
withNullDefault = true;
}
typeAnnotation = typeAnnotation.typeParameters.params[0];
type =
typeAnnotation.type === 'TSTypeReference'
? typeAnnotation.typeName.name
: typeAnnotation.type;
}
return {
name,
optional,
typeAnnotation,
defaultValue,
withNullDefault,
optional: property.optional || topLevelType.optional,
typeAnnotation: topLevelType.type,
defaultValue: topLevelType.defaultValue,
};
}

Просмотреть файл

@ -16,19 +16,21 @@ import type {
EventTypeAnnotation,
} from '../../../CodegenSchema.js';
const {flattenProperties} = require('./componentsUtils');
const {parseTopLevelType} = require('../parseTopLevelType');
import type {TypeDeclarationMap} from '../utils.js';
function getPropertyType(
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
name,
optional: boolean,
optionalProperty: boolean,
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
typeAnnotation,
annotation,
): NamedShape<EventTypeAnnotation> {
if (typeAnnotation.type === 'TSParenthesizedType') {
return getPropertyType(name, optional, typeAnnotation.typeAnnotation);
}
const topLevelType = parseTopLevelType(annotation);
const typeAnnotation = topLevelType.type;
const optional = optionalProperty || topLevelType.optional;
const type =
typeAnnotation.type === 'TSTypeReference'
? typeAnnotation.typeName.name
@ -75,13 +77,6 @@ function getPropertyType(
type: 'FloatTypeAnnotation',
},
};
case 'Readonly':
return getPropertyType(
name,
optional,
typeAnnotation.typeParameters.params[0],
);
case 'TSTypeLiteral':
return {
name,
@ -93,20 +88,6 @@ function getPropertyType(
};
case 'TSUnionType':
// Check for <T | null | undefined>
if (
typeAnnotation.types.some(
t => t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword',
)
) {
const optionalType = typeAnnotation.types.filter(
t => t.type !== 'TSNullKeyword' && t.type !== 'TSUndefinedKeyword',
)[0];
// Check for <(T | T2) | null | undefined>
return getPropertyType(name, true, optionalType);
}
return {
name,
optional,
@ -123,7 +104,7 @@ function getPropertyType(
function findEventArgumentsAndType(
typeAnnotation: $FlowFixMe,
types: TypeMap,
types: TypeDeclarationMap,
bubblingType: void | 'direct' | 'bubble',
paperName: ?$FlowFixMe,
) {
@ -216,49 +197,38 @@ function getEventArgument(argumentProps, name: $FlowFixMe) {
};
}
function findEvent(typeAnnotation: $FlowFixMe, optional: boolean) {
function isEvent(typeAnnotation: $FlowFixMe) {
switch (typeAnnotation.type) {
// Check for T | null | undefined
case 'TSUnionType':
return findEvent(
typeAnnotation.types.filter(
t => t.type !== 'TSNullKeyword' && t.type !== 'TSUndefinedKeyword',
)[0],
optional ||
typeAnnotation.types.some(
t => t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword',
),
);
// Check for (T)
case 'TSParenthesizedType':
return findEvent(typeAnnotation.typeAnnotation, optional);
case 'TSTypeReference':
if (
typeAnnotation.typeName.name !== 'BubblingEventHandler' &&
typeAnnotation.typeName.name !== 'DirectEventHandler'
) {
return null;
return false;
} else {
return {typeAnnotation, optional};
return true;
}
default:
return null;
return false;
}
}
function buildEventSchema(
types: TypeMap,
types: TypeDeclarationMap,
property: EventTypeAST,
): ?EventTypeShape {
const name = property.key.name;
const foundEvent = findEvent(
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
property.optional || false,
types,
);
if (!foundEvent) {
if (!isEvent(topLevelType.type)) {
return null;
}
const {typeAnnotation, optional} = foundEvent;
const name = property.key.name;
const typeAnnotation = topLevelType.type;
const optional = property.optional || topLevelType.optional;
const {argumentProps, bubblingType, paperTopLevelNameDeprecated} =
findEventArgumentsAndType(typeAnnotation, types);
@ -299,15 +269,9 @@ function buildEventSchema(
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
type EventTypeAST = Object;
type TypeMap = {
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
[string]: Object,
...
};
function getEvents(
eventTypeAST: $ReadOnlyArray<EventTypeAST>,
types: TypeMap,
types: TypeDeclarationMap,
): $ReadOnlyArray<EventTypeShape> {
return eventTypeAST
.filter(property => property.type === 'TSPropertySignature')

Просмотреть файл

@ -29,7 +29,7 @@ function buildPropSchema(
if (info == null) {
return null;
}
const {name, optional, typeAnnotation, defaultValue, withNullDefault} = info;
const {name, optional, typeAnnotation, defaultValue} = info;
return {
name,
optional,
@ -37,7 +37,6 @@ function buildPropSchema(
name,
typeAnnotation,
defaultValue,
withNullDefault,
types,
buildPropSchema,
),

Просмотреть файл

@ -29,7 +29,7 @@ function buildStateSchema(
if (info == null) {
return null;
}
const {name, optional, typeAnnotation, defaultValue, withNullDefault} = info;
const {name, optional, typeAnnotation, defaultValue} = info;
return {
name,
optional,
@ -37,7 +37,6 @@ function buildStateSchema(
name,
typeAnnotation,
defaultValue,
withNullDefault,
types,
buildStateSchema,
),

Просмотреть файл

@ -154,16 +154,6 @@ function translateTypeAnnotation(
resolveTypeAnnotation(typeScriptTypeAnnotation, types);
switch (typeAnnotation.type) {
case 'TSParenthesizedType': {
return translateTypeAnnotation(
hasteModuleName,
typeAnnotation.typeAnnotation,
types,
aliasMap,
tryParse,
cxxOnly,
);
}
case 'TSArrayType': {
return translateArrayTypeAnnotation(
hasteModuleName,
@ -231,25 +221,6 @@ function translateTypeAnnotation(
nullable,
);
}
case 'Readonly': {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(
hasteModuleName,
typeAnnotation,
);
const [paramType, isParamNullable] = unwrapNullable(
translateTypeAnnotation(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
types,
aliasMap,
tryParse,
cxxOnly,
),
);
return wrapNullable(nullable || isParamNullable, paramType);
}
case 'Stringish': {
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',

Просмотреть файл

@ -0,0 +1,189 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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-local
* @format
*/
'use strict';
import type {TypeDeclarationMap} from './utils.js';
export type LegalDefaultValues = string | number | boolean | null;
type TopLevelTypeInternal = {
unions: Array<$FlowFixMe>,
optional: boolean,
defaultValue?: LegalDefaultValues,
};
export type TopLevelType = {
type: $FlowFixMe,
optional: boolean,
defaultValue?: LegalDefaultValues,
};
function getValueFromTypes(
value: $FlowFixMe,
types: TypeDeclarationMap,
): $FlowFixMe {
switch (value.type) {
case 'TSTypeReference':
if (types[value.typeName.name]) {
return getValueFromTypes(types[value.typeName.name], types);
} else {
return value;
}
case 'TSTypeAliasDeclaration':
return getValueFromTypes(value.typeAnnotation, types);
default:
return value;
}
}
function isNull(t: $FlowFixMe) {
return t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword';
}
function isNullOrVoid(t: $FlowFixMe) {
return isNull(t) || t.type === 'TSVoidKeyword';
}
function couldBeNumericLiteral(type: string) {
return type === 'Literal' || type === 'NumericLiteral';
}
function couldBeSimpleLiteral(type: string) {
return (
couldBeNumericLiteral(type) ||
type === 'StringLiteral' ||
type === 'BooleanLiteral'
);
}
function evaluateLiteral(
literalNode: $FlowFixMe,
): string | number | boolean | null {
const valueType = literalNode.type;
if (valueType === 'TSLiteralType') {
const literal = literalNode.literal;
if (couldBeSimpleLiteral(literal.type)) {
if (
typeof literal.value === 'string' ||
typeof literal.value === 'number' ||
typeof literal.value === 'boolean'
) {
return literal.value;
}
} else if (
literal.type === 'UnaryExpression' &&
literal.operator === '-' &&
couldBeNumericLiteral(literal.argument.type) &&
typeof literal.argument.value === 'number'
) {
return -literal.argument.value;
}
} else if (isNull(literalNode)) {
return null;
}
throw new Error(
'The default value in WithDefault must be string, number, boolean or null .',
);
}
function handleUnionAndParen(
type: $FlowFixMe,
result: TopLevelTypeInternal,
knownTypes?: TypeDeclarationMap,
): void {
switch (type.type) {
case 'TSParenthesizedType': {
handleUnionAndParen(type.typeAnnotation, result, knownTypes);
break;
}
case 'TSUnionType': {
// the order is important
// result.optional must be set first
for (const t of type.types) {
if (isNullOrVoid(t)) {
result.optional = true;
}
}
for (const t of type.types) {
if (!isNullOrVoid(t)) {
handleUnionAndParen(t, result, knownTypes);
}
}
break;
}
case 'TSTypeReference':
if (type.typeName.name === 'Readonly') {
handleUnionAndParen(type.typeParameters.params[0], result, knownTypes);
} else if (type.typeName.name === 'WithDefault') {
if (result.optional) {
throw new Error(
'WithDefault<> is optional and does not need to be marked as optional. Please remove the union of undefined and/or null',
);
}
if (type.typeParameters.params.length !== 2) {
throw new Error(
'WithDefault requires two parameters: type and default value.',
);
}
if (result.defaultValue !== undefined) {
throw new Error(
'Multiple WithDefault is not allowed nested or in a union type.',
);
}
result.optional = true;
result.defaultValue = evaluateLiteral(type.typeParameters.params[1]);
handleUnionAndParen(type.typeParameters.params[0], result, knownTypes);
} else if (!knownTypes) {
result.unions.push(type);
} else {
const resolvedType = getValueFromTypes(type, knownTypes);
if (
resolvedType.type === 'TSTypeReference' &&
resolvedType.typeName.name === type.typeName.name
) {
result.unions.push(type);
} else {
handleUnionAndParen(resolvedType, result, knownTypes);
}
}
break;
default:
result.unions.push(type);
}
}
function parseTopLevelType(
type: $FlowFixMe,
knownTypes?: TypeDeclarationMap,
): TopLevelType {
let result: TopLevelTypeInternal = {unions: [], optional: false};
handleUnionAndParen(type, result, knownTypes);
if (result.unions.length === 0) {
throw new Error('Union type could not be just null or undefined.');
} else if (result.unions.length === 1) {
return {
type: result.unions[0],
optional: result.optional,
defaultValue: result.defaultValue,
};
} else {
return {
type: {type: 'TSUnionType', types: result.unions},
optional: result.optional,
defaultValue: result.defaultValue,
};
}
}
module.exports = {
parseTopLevelType,
};

Просмотреть файл

@ -11,6 +11,7 @@
'use strict';
const {ParserError} = require('./errors');
const {parseTopLevelType} = require('./parseTopLevelType');
/**
* TODO(T108222691): Use flow-types for @babel/parser
@ -82,18 +83,11 @@ function resolveTypeAnnotation(
};
for (;;) {
// Check for optional type in union e.g. T | null | undefined
if (
node.type === 'TSUnionType' &&
node.types.some(
t => t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword',
)
) {
node = node.types.filter(
t => t.type !== 'TSNullKeyword' && t.type !== 'TSUndefinedKeyword',
)[0];
nullable = true;
} else if (node.type === 'TSTypeReference') {
const topLevelType = parseTopLevelType(node);
nullable = nullable || topLevelType.optional;
node = topLevelType.type;
if (node.type === 'TSTypeReference') {
typeAliasResolutionStatus = {
successful: true,
aliasName: node.typeName.name,
@ -124,21 +118,6 @@ function resolveTypeAnnotation(
};
}
function getValueFromTypes(value: ASTNode, types: TypeDeclarationMap): ASTNode {
switch (value.type) {
case 'TSTypeReference':
if (types[value.typeName.name]) {
return getValueFromTypes(types[value.typeName.name], types);
} else {
return value;
}
case 'TSTypeAliasDeclaration':
return getValueFromTypes(value.typeAnnotation, types);
default:
return value;
}
}
export type ParserErrorCapturer = <T>(fn: () => T) => ?T;
function createParserErrorCapturer(): [
@ -231,7 +210,6 @@ function isModuleRegistryCall(node: $FlowFixMe): boolean {
}
module.exports = {
getValueFromTypes,
resolveTypeAnnotation,
createParserErrorCapturer,
getTypes,