import { GenericTypeAnnotation, TSIndexedAccessType, TSTypeLiteral, TSTypeOperator, TSTypeParameterInstantiation, TSTypeQuery, TSTypeReference, identifier, isGenericTypeAnnotation, isIdentifier, isTSObjectKeyword, isTSPropertySignature, isTSTypeLiteral, isTSTypeQuery, isTSTypeReference, isTypeAnnotation, stringLiteral, tsIndexedAccessType, tsLiteralType, tsTypeOperator, tsTypeParameterInstantiation, tsTypeQuery, tsTypeReference, tsUnionType, } from '@babel/types'; import { NodePath } from '@babel/traverse'; import { UnexpectedError } from '../../util/error'; import { convertIdentifier } from './identifier'; type UtilConvertor = (typeParameters: TSTypeParameterInstantiation) => R; export const convertCallUtility: UtilConvertor = typeParameters => tsTypeReference(identifier('ReturnType'), typeParameters); export function convertClassUtil( typeParameters: TSTypeParameterInstantiation, path: NodePath, ): TSTypeQuery { // Class has exactly one type parameter const typeParam = typeParameters.params[0]; // type ClassType = Class -> type ClassType = typeof C if (isTSTypeReference(typeParam)) { return tsTypeQuery(typeParam.typeName); } // Let c be an instance of the C class: // type ClassType = Class -> type ClassType = typeof C; if (isTSTypeQuery(typeParam) && isIdentifier(typeParam.exprName)) { const binding = path.scope.getBinding(typeParam.exprName.name); if (binding && isTypeAnnotation(binding.identifier.typeAnnotation)) { const flowType = binding.identifier.typeAnnotation.typeAnnotation; if (isGenericTypeAnnotation(flowType)) { return tsTypeQuery(convertIdentifier(flowType.id)); } } } throw new UnexpectedError( `Unknown type parameter for Class utility: ${typeParam.type}.`, ); } export const convertDiffUtil: UtilConvertor = typeParameters => { // $Diff takes exactly two type parameters const secondParam = typeParameters.params[1]; if (isTSTypeReference(secondParam) && isIdentifier(secondParam.typeName)) { secondParam.typeName.name = `keyof ${secondParam.typeName.name}`; } // Literals and `object` keyword need to be wrapped in a TypeOperator if (isTSTypeLiteral(secondParam) || isTSObjectKeyword(secondParam)) { const typeOperator = tsTypeOperator(secondParam); typeOperator.operator = 'keyof'; typeParameters.params[1] = typeOperator; } return tsTypeReference(identifier('Omit'), typeParameters); }; export const convertElementTypeUtil: UtilConvertor = typeParameters => tsIndexedAccessType(typeParameters.params[0], typeParameters.params[1]); export const convertExactUtil: UtilConvertor< TSTypeReference | TSTypeLiteral > = typeParameters => { const typeParam = typeParameters.params[0]; if (isTSTypeReference(typeParam) || isTSTypeLiteral(typeParam)) { return typeParam; } throw new UnexpectedError( `Unexpected type parameter for $Exact: ${typeParam.type}`, ); }; export const convertKeysUtil: UtilConvertor = typeParameters => { const typeOperator = tsTypeOperator(typeParameters.params[0]); typeOperator.operator = 'keyof'; return typeOperator; }; export const convertNonMaybeTypeUtil: UtilConvertor = typeParameters => tsTypeReference(identifier('NonNullable'), typeParameters); export const convertPropertyTypeUtil: UtilConvertor = typeParameters => tsIndexedAccessType(typeParameters.params[0], typeParameters.params[1]); export const convertReadOnlyArrayUtil: UtilConvertor = typeParameters => tsTypeReference(identifier('ReadonlyArray'), typeParameters); export const convertRestUtil: UtilConvertor = typeParameters => { const [typeName, restArg] = typeParameters.params; const omitArg = isTSTypeLiteral(restArg) ? tsUnionType( restArg.members.map(typeElem => { return tsLiteralType( stringLiteral( isTSPropertySignature(typeElem) && isIdentifier(typeElem.key) ? typeElem.key.name : '', ), ); }), ) : restArg; return tsTypeReference( identifier('Omit'), tsTypeParameterInstantiation([typeName, omitArg]), ); }; export const convertShapeUtil: UtilConvertor = typeParameters => tsTypeReference(identifier('Partial'), typeParameters); export const convertReadOnlyUtil: UtilConvertor = typeParameters => tsTypeReference(identifier('Readonly'), typeParameters);