@cosmology/ast
Version:
Cosmos TypeScript AST generation
433 lines (372 loc) • 12.5 kB
text/typescript
import * as t from '@babel/types';
import { ProtoType, ProtoField, TelescopeLogLevel } from '@cosmology/types';
import { identifier, tsPropertySignature, functionDeclaration, makeCommentBlock } from '../../../utils';
import { ProtoParseContext } from '../../context';
import {
getBaseCreateTypeFuncName,
getFieldOptionality,
getFieldOptionalityForDefaults,
getOneOfs
} from '../types';
import {
SymbolNames,
getDefaultTSTypeFromProtoType,
getFieldTypeReference,
TelescopeBaseTypes,
getTSType
} from '../../types';
import { getTypeUrlWithPkgAndName, getTypeUrl } from '../../amino';
import { TraversalSymbol } from '@cosmology/types';
const getProtoField = (
context: ProtoParseContext,
field: ProtoField,
type: TelescopeBaseTypes = 'Msg'
) => {
let ast: any = 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: ProtoParseContext,
name: string,
proto: ProtoType,
type: TelescopeBaseTypes = '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: ProtoParseContext,
name: string,
proto: ProtoType
) => {
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
)
])));
};
interface FieldInfo {
isOneOf: boolean,
field: ProtoField,
name: string,
lookupInterface: string,
isAnyType: boolean,
symbols: TraversalSymbol[]
}
export const createProtoInterfaceEncodedType = (
context: ProtoParseContext,
name: string,
proto: ProtoType
) => {
const MsgName = SymbolNames.Msg(name);
const EncodedMsgName = SymbolNames.Encoded(name);
const oneOfs = getOneOfs(proto);
const implementsAcceptsAny = context.pluginValue('interfaces.enabled');
const fieldsWithInfo: FieldInfo[] = 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: ProtoParseContext,
name: string,
proto: ProtoType
) => {
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))
))
};