@cosmology/ast
Version:
Cosmos TypeScript AST generation
228 lines (227 loc) • 10.3 kB
JavaScript
import * as t from '@babel/types';
import { TelescopeLogLevel } from '@cosmology/types';
import { identifier, tsPropertySignature, functionDeclaration, makeCommentBlock } from '../../../utils';
import { getBaseCreateTypeFuncName, getFieldOptionality, getFieldOptionalityForDefaults, getOneOfs } from '../types';
import { SymbolNames, getDefaultTSTypeFromProtoType, getFieldTypeReference, getTSType } from '../../types';
import { getTypeUrlWithPkgAndName, getTypeUrl } from '../../amino';
const getProtoField = (context, field, type = 'Msg') => {
let ast = null;
const fieldRef = getFieldTypeReference(context, field, type);
ast = fieldRef.ast;
const isTypeCastableAnyType = fieldRef.isTypeCastableAnyType;
if (field.rule === 'repeated') {
ast = t.tsArrayType(ast);
if (isTypeCastableAnyType) {
ast = t.tsUnionType([
ast,
t.tsArrayType(t.tsTypeReference(t.identifier('Any')))
]);
}
}
if (field.keyType) {
ast = t.tsUnionType([
t.tsTypeLiteral([
t.tsIndexSignature([
identifier('key', t.tsTypeAnnotation(getTSType(context, field.keyType)))
], t.tsTypeAnnotation(ast))
])
]);
}
return ast;
};
export const createProtoType = (context, name, proto, type = 'Msg') => {
const oneOfs = getOneOfs(proto);
// MARKED AS COSMOS SDK specific code
const optionalityMap = {};
// if a param is found to be a route parameter, we assume it's required
// if a param is found to be a query parameter, we assume it's optional
if (context.pluginValue('prototypes.optionalQueryParams') && context.store.requests[name]) {
const svc = context.store.requests[name];
if (svc.info) {
svc.info.queryParams.map(param => {
optionalityMap[param] = true;
});
}
}
// hard-code optionality for pagination
if (context.pluginValue('prototypes.optionalPageRequests')) {
if (context.ref.proto.package === 'cosmos.base.query.v1beta1') {
if (name === 'PageRequest') {
optionalityMap['key'] = true;
optionalityMap['offset'] = true;
optionalityMap['limit'] = true;
optionalityMap['count_total'] = true;
optionalityMap['countTotal'] = true;
optionalityMap['reverse'] = true;
}
if (name === 'PageResponse') {
optionalityMap['next_key'] = true;
optionalityMap['nextKey'] = true;
}
}
}
const MsgName = SymbolNames[type](name);
const fields = [];
if (context.pluginValue('prototypes.addTypeUrlToDecoders') &&
((context.pluginValue('interfaces.enabled') &&
proto.options?.['(cosmos_proto.implements_interface)']) ||
(context.ref.proto.package === 'google.protobuf'
&& name === 'Any'))) {
const typeUrl = getTypeUrlWithPkgAndName(context.ref.proto.package, name);
const typeAnnotation = (context.ref.proto.package === 'google.protobuf'
&& name === 'Any') ? t.tsTypeAnnotation(t.tsUnionType([
t.tsLiteralType(t.stringLiteral(typeUrl)),
t.tsStringKeyword()
])) : t.tsTypeAnnotation(t.tsLiteralType(t.stringLiteral(typeUrl)));
fields.push(tsPropertySignature(t.identifier('$typeUrl'), typeAnnotation, true));
}
[].push.apply(fields, Object.keys(proto.fields).reduce((m, fieldName) => {
const isOneOf = oneOfs.includes(fieldName);
const field = proto.fields[fieldName];
const isOptional = getFieldOptionalityForDefaults(context, field, isOneOf);
// optionalityMap is coupled to API requests
const orig = field.options?.['(telescope:orig)'] ?? fieldName;
let optional = false;
if (optionalityMap[orig]) {
optional = true;
}
// let fieldNameWithCase = options.useOriginalCase ? orig : fieldName;
let fieldNameWithCase = type === 'SDKType' ? orig : fieldName;
const protoField = getProtoField(context, field, type);
const propSig = tsPropertySignature(t.identifier(fieldNameWithCase), t.tsTypeAnnotation(protoField),
// optional || getFieldOptionality(context, field, isOneOf)
isOptional || optional);
const comments = [];
if (field.comment &&
// no comment for derivative types
(type === 'Msg')) {
comments.push(makeCommentBlock(field.comment));
}
if (field.options?.deprecated) {
comments.push(makeCommentBlock('@deprecated'));
}
if (comments.length) {
propSig.leadingComments = comments;
}
m.push(propSig);
return m;
}, []));
// declaration
const declaration = t.exportNamedDeclaration(t.tsInterfaceDeclaration(t.identifier(MsgName), null, [], t.tsInterfaceBody(fields)));
const comments = [];
if (proto.comment) {
comments.push(makeCommentBlock(proto.comment));
}
if (proto.options?.deprecated) {
comments.push(makeCommentBlock('@deprecated'));
}
if (comments.length) {
declaration.leadingComments = comments;
}
return declaration;
};
export const createProtoTypeType = (context, name, proto) => {
const ProtoMsgName = SymbolNames.ProtoMsg(name);
const typeUrl = getTypeUrl(context.ref.proto, proto);
const typ = typeUrl ? t.tsLiteralType(t.stringLiteral(typeUrl)) : t.tsTypeReference(t.identifier('string'));
return t.exportNamedDeclaration(t.tsInterfaceDeclaration(t.identifier(ProtoMsgName), null, [], t.tsInterfaceBody([
tsPropertySignature(t.identifier(context.options.prototypes.parser.keepCase ? 'type_url' : 'typeUrl'), t.tsTypeAnnotation(typ), false),
tsPropertySignature(t.identifier('value'), t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Uint8Array'))), false)
])));
};
export const createProtoInterfaceEncodedType = (context, name, proto) => {
const MsgName = SymbolNames.Msg(name);
const EncodedMsgName = SymbolNames.Encoded(name);
const oneOfs = getOneOfs(proto);
const implementsAcceptsAny = context.pluginValue('interfaces.enabled');
const fieldsWithInfo = Object.keys(proto.fields)
.reduce((m, fieldName) => {
const isOneOf = oneOfs.includes(fieldName);
const field = proto.fields[fieldName];
const lookupInterface = field.options?.['(cosmos_proto.accepts_interface)'];
const isAnyType = field.parsedType?.type === 'Type' && field.parsedType?.name === 'Any';
// 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`);
}
}
const isAnyInterface = isAnyType && lookupInterface && implementsAcceptsAny && symbols;
if (!isAnyInterface)
return m;
// ONLY INTERFACES!
return [...m, {
isOneOf,
field,
name: fieldName,
lookupInterface,
isAnyType,
symbols
}];
}, []).filter(Boolean);
if (!fieldsWithInfo.length)
return;
const interfaceFields = fieldsWithInfo.map(fieldInfo => {
return t.tsLiteralType(t.stringLiteral(fieldInfo.name));
});
const fields = fieldsWithInfo.map(fieldsInfo => {
const { field, isOneOf, name: fieldName } = fieldsInfo;
let optional = false;
const protoField = getProtoField(context, field, 'ProtoMsg');
const propSig = tsPropertySignature(t.identifier(fieldName), t.tsTypeAnnotation(protoField), optional || getFieldOptionality(context, field, isOneOf));
const comments = [];
if (field.comment) {
comments.push(makeCommentBlock(field.comment));
}
if (field.options?.deprecated) {
comments.push(makeCommentBlock('@deprecated'));
}
if (comments.length) {
propSig.leadingComments = comments;
}
return propSig;
});
return t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier(EncodedMsgName), null, t.tsIntersectionType([
t.tsTypeReference(t.identifier('Omit'), t.tsTypeParameterInstantiation([
t.tsTypeReference(t.identifier(MsgName)),
interfaceFields.length > 1 ? t.tsUnionType([
...interfaceFields
]) : interfaceFields[0]
])),
t.tsTypeLiteral([
...fields
])
])));
};
export const createCreateProtoType = (context, name, proto) => {
const oneOfs = getOneOfs(proto);
const fields = [];
if (context.pluginValue('prototypes.addTypeUrlToDecoders') &&
((context.pluginValue('interfaces.enabled') &&
proto.options?.['(cosmos_proto.implements_interface)']) ||
(context.ref.proto.package === 'google.protobuf'
&& name === 'Any'))) {
const typeUrl = getTypeUrlWithPkgAndName(context.ref.proto.package, name);
fields.push(t.objectProperty(t.identifier('$typeUrl'), t.stringLiteral(typeUrl)));
}
[].push.apply(fields, Object.keys(proto.fields).map(key => {
const isOneOf = oneOfs.includes(key);
const isOptional = getFieldOptionality(context, proto.fields[key], isOneOf);
return {
name: key,
...proto.fields[key],
isOneOf,
isOptional
};
})
.map(field => {
return t.objectProperty(t.identifier(field.name), getDefaultTSTypeFromProtoType(context, field, field.isOneOf));
}));
return functionDeclaration(t.identifier(getBaseCreateTypeFuncName(name)), [], t.blockStatement([
t.returnStatement(t.objectExpression([
...fields,
]))
]), false, false, t.tsTypeAnnotation(t.tsTypeReference(t.identifier(name))));
};