import { PluginPass } from '@babel/core' ; import { ObjectTypeAnnotation , ObjectTypeProperty , TSMethodSignature , TSObjectKeyword , TSPropertySignature , TSTypeElement , TSTypeLiteral , identifier , isFunctionTypeAnnotation , isNullableTypeAnnotation , isNumberTypeAnnotation , isObjectTypeProperty , isObjectTypeSpreadProperty , isStringTypeAnnotation , tsCallSignatureDeclaration , tsIndexSignature , tsMethodSignature , tsNumberKeyword , tsObjectKeyword , tsPropertySignature , tsStringKeyword , tsTypeAnnotation , tsTypeLiteral , } from '@babel/types' ; import { convertFlowType } from './flow-type' ; import { UnexpectedError } from '../../util/error' ; import { WARNINGS , logWarning } from '../util/warnings' ; import { convertFunctionTypeAnnotation , functionTypeParametersToIdentifiers , } from './function' ; import { convertNullableTypeAnnotation } from './nullable' ; import { convertTypeParameterDeclaration } from './type-parameter' ; function propIsEmpty ( prop ? ) { return ! prop || prop . length === 0 ; } function createMethodSignature ( prop , state ) { const { key , optional , value } = prop ; if ( ! isFunctionTypeAnnotation ( value ) ) { throw new UnexpectedError ( `prop.value must be a FunctionTypeAnnotation.` ) ; } const functionType = convertFunctionTypeAnnotation ( value , state ) ; const methodSignature = tsMethodSignature ( key , functionType . typeParameters , functionType . parameters , functionType . typeAnnotation ) ; methodSignature . optional = optional ; return methodSignature ; } function createPropertySignature ( prop , state ) { const { key , optional , variance } = prop ; const typeAnnotation = isNullableTypeAnnotation ( prop . value ) && optional ? tsTypeAnnotation ( convertNullableTypeAnnotation ( prop . value , state , { skipUndefined : true , } ) ) : tsTypeAnnotation ( convertFlowType ( prop . value , state ) ) ; const propSignature = tsPropertySignature ( key , typeAnnotation ) ; propSignature . optional = optional ; propSignature . readonly = variance && variance . kind === 'plus' ; if ( variance && variance . kind === 'minus' ) { logWarning ( WARNINGS . objectTypeProperty . variance , state . file . code , variance . loc ) ; } return propSignature ; } function convertObjectTypeCallProperties ( signatures , node , state ) { const { callProperties } = node ; if ( callProperties ) { callProperties . forEach ( callProp => { if ( ! isFunctionTypeAnnotation ( callProp . value ) ) { return ; } const { value } = callProp ; const typeParameters = convertTypeParameterDeclaration ( value . typeParameters , state ) ; const params = functionTypeParametersToIdentifiers ( value . params , state ) || [ ] ; const typeAnnotation = tsTypeAnnotation ( convertFlowType ( value . returnType , state ) ) ; signatures . push ( tsCallSignatureDeclaration ( typeParameters , params , typeAnnotation ) ) ; } ) ; } return signatures ; } function convertObjectTypeIndexers ( signatures , node , state ) { const { indexers } = node ; if ( indexers ) { indexers . forEach ( indexer => { const { id , key } = indexer ; const typeAnnotation = tsTypeAnnotation ( convertFlowType ( indexer . value , state ) ) ; const isValidKeyType = isNumberTypeAnnotation ( key ) || isStringTypeAnnotation ( key ) ; if ( isValidKeyType ) { const tsKey = identifier ( id ? id . name : 'key' ) ; tsKey . typeAnnotation = tsTypeAnnotation ( convertFlowType ( key , state ) ) ; signatures . push ( tsIndexSignature ( [ tsKey ] , typeAnnotation ) ) ; } else { signatures . push ( ... [ tsNumberKeyword ( ) , tsStringKeyword ( ) ] . map ( type => { const tsKey = identifier ( id ? id . name : 'key' ) ; tsKey . typeAnnotation = tsTypeAnnotation ( type ) ; return tsIndexSignature ( [ tsKey ] , typeAnnotation ) ; } ) ) ; logWarning ( WARNINGS . indexSignatures . invalidKey , state . file . code , indexer . loc ) ; } } ) ; } return signatures ; } export function convertObjectTypeAnnotation ( node , state ) { const { callProperties , indexers , internalSlots , properties } = node ; if ( propIsEmpty ( callProperties ) && propIsEmpty ( indexers ) && propIsEmpty ( internalSlots ) && propIsEmpty ( properties ) ) { return tsObjectKeyword ( ) ; } let signatures = [ ] ; properties . forEach ( prop => { if ( isObjectTypeProperty ( prop ) ) { if ( isFunctionTypeAnnotation ( prop . value ) && prop . method ) { signatures . push ( createMethodSignature ( prop , state ) ) ; } else { signatures . push ( createPropertySignature ( prop , state ) ) ; } } if ( isObjectTypeSpreadProperty ( prop ) ) { logWarning ( WARNINGS . objectTypeSpreadProperty , state . file . code , prop . argument . loc ) ; } } ) ; signatures = convertObjectTypeCallProperties ( signatures , node , state ) ; signatures = convertObjectTypeIndexers ( signatures , node , state ) ; return tsTypeLiteral ( signatures ) ; }