UNPKG

@cosmology/ast

Version:
442 lines (441 loc) 15.7 kB
import * as t from '@babel/types'; import { ENUM_PROTO2_DEFAULT, ENUM_PROTO3_DEFAULT, TelescopeLogLevel } from '@cosmology/types'; import { getProtoFieldTypeName, TypeLong } from '../utils'; import { getFieldOptionalityForDefaults, GOOGLE_TYPES, SCALAR_TYPES } from './proto'; export const getFieldNames = (field) => { const propName = field.options?.['(telescope:name)'] ?? field.name; const origName = field.options?.['(telescope:orig)'] ?? field.name; return { propName, origName }; }; const getSymbolName = (name, type = 'Msg') => { let typeNameSuffix; switch (type) { case 'ProtoMsg': typeNameSuffix = 'ProtoMsg'; break; case 'AminoMsg': typeNameSuffix = 'AminoMsg'; break; case 'Amino': typeNameSuffix = 'Amino'; break; case 'SDKType': typeNameSuffix = 'SDKType'; break; case 'Encoded': typeNameSuffix = 'Encoded'; break; case 'Msg': default: } return [name, typeNameSuffix].filter(Boolean).join(''); }; export const SymbolNames = { Msg: (name) => getSymbolName(name, 'Msg'), SDKType: (name) => getSymbolName(name, 'SDKType'), ProtoMsg: (name) => getSymbolName(name, 'ProtoMsg'), AminoMsg: (name) => getSymbolName(name, 'AminoMsg'), Amino: (name) => getSymbolName(name, 'Amino'), Encoded: (name) => getSymbolName(name, 'Encoded'), }; export const getFieldTypeReference = (context, field, type = 'Msg') => { let ast = null; let typ = null; if (SCALAR_TYPES.includes(field.type)) { // return on scalar typ = getTSTypeForProto(context, field); return { ast: typ }; } else if (GOOGLE_TYPES.includes(field.type)) { typ = getTSTypeFromGoogleType(context, field.type, type); } else { const propName = getProtoFieldTypeName(context, field); const MsgName = field.parsedType?.type === 'Enum' ? propName : SymbolNames[type](propName); typ = t.tsTypeReference(t.identifier(MsgName)); } const implementsAcceptsAny = context.pluginValue('interfaces.enabled'); const lookupInterface = field.options?.['(cosmos_proto.accepts_interface)']; const isAnyType = field.parsedType?.type === 'Type' && field.parsedType?.name === 'Any'; const isArray = field.rule === 'repeated'; const isBaseType = type === 'Msg'; const isEncodedType = type === 'ProtoMsg'; const isSDKType = type === 'SDKType'; // MARKED AS NOT DRY (symbols) let symbols = null; if (implementsAcceptsAny && lookupInterface) { symbols = context.store._symbols.filter(s => s.implementsType === lookupInterface && s.ref === context.ref.filename); if (!symbols.length && context.options.logLevel >= TelescopeLogLevel.Warn) { console.warn(`[WARN] ${lookupInterface} is accepted but not implemented`); } } if (!isBaseType) { if (['ProtoMsg', 'SDKType'].includes(type)) { symbols?.forEach(s => { context.addImportDerivative({ type, symbol: s }); }); } // main type could be Any if (['SDKType'].includes(type) && // no derivatives for Enums! field.parsedType.type === 'Type') { context.addImportDerivative({ type, symbol: { ref: context.ref.filename, readAs: field.parsedType.name, source: field.import, symbolName: field.parsedType.name, type: 'import' }, }); } } // cast Any types! const isAnyInterface = isAnyType && lookupInterface && implementsAcceptsAny && symbols; const isTypeCastable = isAnyInterface && isBaseType; const isProtoTypeCastable = isAnyInterface && isEncodedType; const isSDKTypeCastable = isAnyInterface && isSDKType; const isNonArrayNullableType = field.parsedType?.type === 'Type' && field.rule !== 'repeated' && context.pluginValue('prototypes.allowUndefinedTypes'); if (isTypeCastable) { const tp = symbols.map(a => t.tsTypeReference(t.identifier(a.readAs))); tp.push(typ); if (context.pluginValue('interfaces.useUnionTypes')) { if (!isArray) { tp.push(t.tsUndefinedKeyword()); } ast = t.tsUnionType(tp); } else { // intersections if (isArray) { ast = t.tsIntersectionType(tp); } else { ast = t.tsUnionType([ t.tsIntersectionType(tp), t.tsUndefinedKeyword() ]); } } } else if (isProtoTypeCastable) { const tp = symbols.map(a => t.tsTypeReference(t.identifier(SymbolNames.ProtoMsg(a.readAs)))); symbols.forEach(a => { context.addImportDerivative({ type: 'ProtoMsg', symbol: a }); }); tp.push(typ); if (!isArray) { tp.push(t.tsUndefinedKeyword()); } ast = t.tsUnionType(tp); } else if (isSDKTypeCastable) { const tp = symbols.map(a => t.tsTypeReference(t.identifier(SymbolNames.SDKType(a.readAs)))); symbols.forEach(a => { context.addImportDerivative({ type: 'SDKType', symbol: a }); }); tp.push(typ); if (!isArray) { tp.push(t.tsUndefinedKeyword()); } ast = t.tsUnionType(tp); } else if (isNonArrayNullableType) { // regular types! ast = t.tsUnionType([ typ, t.tsUndefinedKeyword() ]); } else { ast = typ; } return { ast, isTypeCastableAnyType: isTypeCastable }; }; export const getFieldAminoTypeReference = (context, field) => { let ast = null; let typ = null; if (SCALAR_TYPES.includes(field.type)) { // return on scalar typ = getTSTypeForAmino(context, field); return typ; } else if (GOOGLE_TYPES.includes(field.type)) { typ = getTSTypeFromGoogleType(context, field.type, 'Amino'); } else { const propName = getProtoFieldTypeName(context, field); // enums don't need suffixes, etc. const MsgName = field.parsedType?.type === 'Enum' ? propName : SymbolNames.Amino(propName); typ = t.tsTypeReference(t.identifier(MsgName)); } if (field.parsedType?.type === 'Type') { const typeName = field.isNestedMsg ? field.importedName : field.parsedType.name; context.addImportDerivative({ type: 'Amino', symbol: { ref: context.ref.filename, readAs: typeName, source: field.import, symbolName: typeName, type: 'import' }, }); } if (field.parsedType?.type === 'Type' && field.rule !== 'repeated' && context.pluginValue('prototypes.allowUndefinedTypes')) { // NOTE: unfortunately bc of defaults... ast = t.tsUnionType([ typ, t.tsUndefinedKeyword() ]); } else { ast = typ; } return ast; }; export const getTSType = (context, type) => { switch (type) { case 'string': return t.tsStringKeyword(); case 'double': case 'float': case 'int32': case 'uint32': case 'sint32': case 'fixed32': case 'sfixed32': return t.tsNumberKeyword(); case 'int64': case 'uint64': case 'sint64': case 'fixed64': case 'sfixed64': TypeLong.addUtil(context); return t.tsTypeReference(TypeLong.getPropIdentifier(context)); case 'bytes': return t.tsTypeReference(t.identifier('Uint8Array')); case 'bool': return t.tsBooleanKeyword(); default: throw new Error('getTSType() type not found'); } ; }; export const getTSAminoType = (context, type, options) => { switch (type) { case 'string': return t.tsStringKeyword(); case 'double': case 'float': case 'int32': case 'uint32': case 'sint32': case 'fixed32': case 'sfixed32': return t.tsNumberKeyword(); case 'int64': case 'uint64': case 'sint64': case 'fixed64': case 'sfixed64': return t.tsStringKeyword(); case 'bytes': // (gogoproto.customname) = "WASMByteCode", if (options?.["(gogoproto.customname)"] === "WASMByteCode") { return t.tsStringKeyword(); } return t.tsTypeReference(t.identifier('Uint8Array')); case 'bool': return t.tsBooleanKeyword(); default: throw new Error('getTSType() type not found'); } ; }; export const getTSTypeFromGoogleType = (context, type, options = 'Msg') => { const identifier = (str) => { return t.identifier(SymbolNames[options](str)); }; switch (type) { case 'google.protobuf.Timestamp': if (options === 'Amino' || options === 'AminoMsg') { return t.tsStringKeyword(); } switch (context.pluginValue('prototypes.typingsFormat.timestamp')) { case 'timestamp': return t.tsTypeReference(identifier('Timestamp')); case 'date': default: return t.tsTypeReference(t.identifier('Date')); } case 'google.protobuf.Duration': switch (context.pluginValue('prototypes.typingsFormat.duration')) { case 'duration': return t.tsTypeReference(identifier('Duration')); case 'string': default: return t.tsStringKeyword(); } case 'google.protobuf.Any': return t.tsTypeReference(identifier('Any')); default: throw new Error('getTSTypeFromGoogleType() type not found'); } ; }; export const getTSTypeForAmino = (context, field) => { switch (field.type) { case 'bytes': if (field.options?.["(gogoproto.casttype)"] === "RawContractMessage") { return t.tsAnyKeyword(); } else { return t.tsStringKeyword(); } default: return getTSAminoType(context, field.type); } ; }; export const getTSTypeForProto = (context, field) => { return getTSType(context, field.type); }; export const getDefaultTSTypeFromProtoType = (context, field, isOneOf, useNullForOptionals = false) => { const isOptional = getFieldOptionalityForDefaults(context, field, isOneOf); const setDefaultCustomTypesToUndefined = context.pluginValue('prototypes.typingsFormat.setDefaultCustomTypesToUndefined'); if (field.rule === 'repeated') { return t.arrayExpression([]); } if (field.keyType) { return t.objectExpression([]); } if (isOptional) { return useNullForOptionals ? t.nullLiteral() : t.identifier('undefined'); } if (field.parsedType?.type === 'Enum') { const enumDefault = getPreferredEnumDefault(context, field); return t.numericLiteral(enumDefault); } switch (field.type) { case 'string': return t.stringLiteral(''); case 'double': case 'float': case 'int32': case 'uint32': case 'sint32': case 'fixed32': case 'sfixed32': return t.numericLiteral(0); case 'uint64': TypeLong.addUtil(context); return TypeLong.getUZero(context); case 'int64': case 'sint64': case 'fixed64': case 'sfixed64': TypeLong.addUtil(context); return TypeLong.getZero(context); case 'bytes': return t.newExpression(t.identifier('Uint8Array'), []); case 'bool': return t.booleanLiteral(false); // OTHER TYPES case 'google.protobuf.Timestamp': if (setDefaultCustomTypesToUndefined) { return t.identifier('undefined'); } else { const timestampType = context.pluginValue('prototypes.typingsFormat.timestamp'); switch (timestampType) { case 'timestamp': return t.callExpression(t.memberExpression(t.identifier('Timestamp'), t.identifier('fromPartial')), [t.objectExpression([])]); case 'date': return t.newExpression(t.identifier('Date'), []); default: return t.identifier('undefined'); } } // TODO: add cases for this later on case 'google.protobuf.Duration': if (setDefaultCustomTypesToUndefined) { return t.identifier('undefined'); } else { return getDefaultTSTypeFromProtoTypeDefault(context, field); } case 'google.protobuf.Any': if (setDefaultCustomTypesToUndefined) { return t.identifier('undefined'); } else { return getDefaultTSTypeFromProtoTypeDefault(context, field); } case 'cosmos.base.v1beta1.Coin': if (setDefaultCustomTypesToUndefined) { return t.identifier('undefined'); } else { return getDefaultTSTypeFromProtoTypeDefault(context, field); } case 'cosmos.base.v1beta1.Coins': return t.arrayExpression([]); default: if (!field.type) { console.warn('Undefined! Can\'t get field of type:', field); return t.identifier('undefined'); } else { return getDefaultTSTypeFromProtoTypeDefault(context, field); } } ; }; export const getDefaultTSTypeFromAminoTypeDefault = (context, field) => { const typeName = getProtoFieldTypeName(context, field); return t.callExpression(t.memberExpression(t.identifier(typeName), t.identifier("toAmino")), [ t.callExpression(t.memberExpression(t.identifier(getProtoFieldTypeName(context, field)), t.identifier("fromPartial")), [t.objectExpression([])]), ]); }; function getDefaultTSTypeFromProtoTypeDefault(context, field) { return t.callExpression(t.memberExpression(t.identifier(getProtoFieldTypeName(context, field)), t.identifier('fromPartial')), [t.objectExpression([])]); } function getPreferredEnumDefault(context, field) { const autoFixUndefinedEnumDefault = context.pluginValue('prototypes.typingsFormat.autoFixUndefinedEnumDefault'); if (autoFixUndefinedEnumDefault) { const typeName = getProtoFieldTypeName(context, field); return context.getDefaultOrExistingSmallestEnumValue(getPackage(field), typeName); } else { if (context.ref.proto?.syntax === 'proto2') { return ENUM_PROTO2_DEFAULT; } else { return ENUM_PROTO3_DEFAULT; } } } function getPackage(field) { const pkgs = field.scope?.flat(); return pkgs ? pkgs[0] : ''; }