@cosmology/ast
Version:
Cosmos TypeScript AST generation
269 lines (268 loc) • 13.9 kB
JavaScript
import * as t from '@babel/types';
import { arrowFunctionExpression, classDeclaration, classMethod, classProperty, commentBlock, identifier, tsMethodSignature } from '../../../../utils';
import { camel, getServiceImplement } from '@cosmology/utils';
import { processRpcComment, returnReponseType, cleanType, optionalBool } from '../utils/rpc';
import { BinaryCoder } from '../../../../utils/binary-coder-expression';
const rpcMethodDefinition = (name, svc, trailingComments, leadingComments) => {
const requestType = svc.requestType;
const responseType = svc.responseType;
const fieldNames = Object.keys(svc.fields ?? {});
const hasParams = fieldNames.length > 0;
const optional = optionalBool(hasParams, fieldNames);
const methodArgs = identifier('request', t.tsTypeAnnotation(t.tsTypeReference(t.identifier(requestType))), optional);
return tsMethodSignature(t.identifier(name), null, [
methodArgs
], returnReponseType(responseType), trailingComments, leadingComments);
};
const rpcTxMethodDefinition = (name, svc, trailingComments, leadingComments) => {
const requestType = svc.requestType;
const responseType = t.tsTypeReference(t.identifier("DeliverTxResponse"));
const methodArgs = [
identifier("signerAddress", t.tsTypeAnnotation(t.tsStringKeyword())),
identifier("message", t.tsTypeAnnotation(t.tsTypeReference(t.identifier(requestType)))),
identifier("fee", t.tsTypeAnnotation(t.tsUnionType([
t.tsNumberKeyword(),
t.tsTypeReference(t.identifier("StdFee")),
t.tsLiteralType(t.stringLiteral("auto")),
]))),
identifier("memo", t.tsTypeAnnotation(t.tsStringKeyword()), true),
];
return tsMethodSignature(t.identifier(name), null, methodArgs, returnReponseType(responseType), trailingComments, leadingComments);
};
// this.Accounts = this.Accounts.bind(this);
// MARKED AS NOT DRY (used in rpc/lcd)
const bindThis = (name) => {
return t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier(name)), t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier(name)), t.identifier('bind')), [
t.thisExpression()
])));
};
// const data = QueryAccountsRequest.encode(request).finish();
const encodeData = (name) => {
return t.variableDeclaration('const', [
t.variableDeclarator(t.identifier('data'), t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.identifier(name), t.identifier('encode')), [
t.identifier('request')
]), t.identifier('finish')), []))
]);
};
// const promise = this.rpc.request("cosmos.auth.v1beta1.Query", "Accounts", data);
const promiseRequest = (name, packageImportName) => {
name = cleanType(name);
return t.variableDeclaration('const', [
t.variableDeclarator(t.identifier('promise'), t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('rpc')), t.identifier('request')), [
t.stringLiteral(packageImportName),
t.stringLiteral(name),
t.identifier('data')
]))
]);
};
// return promise.then((data) => QueryAccountsResponse.decode(new _m0.Reader(data)));
const returnPromise = (name, context) => {
name = cleanType(name);
return t.returnStatement(t.callExpression(t.memberExpression(t.identifier('promise'), t.identifier('then')), [
t.arrowFunctionExpression([
t.identifier('data')
], t.callExpression(t.memberExpression(t.identifier(name), t.identifier('decode')), [
t.newExpression(BinaryCoder.getReaderMemberExp(context), [
t.identifier('data')
]),
...(context.options.interfaces.enabled && context.options.interfaces.useUseInterfacesParams ? [
t.identifier('undefined'),
t.identifier('useInterfaces')
] : []),
]))
]));
};
const makeComment = (comment) => {
return [{ type: 'CommentBlock', value: ` ${comment} ` }];
};
const rpcClassMethod = (context, name, msg, svc, packageImport) => {
const requestType = svc.requestType;
const responseType = svc.responseType;
const comment = svc.comment ?? svc.name;
let methodArgs = identifier('request', t.tsTypeAnnotation(t.tsTypeReference(t.identifier(requestType))));
const fieldNames = Object.keys(svc.fields ?? {});
const hasParams = fieldNames.length > 0;
// if no params, then let's default to empty object for cleaner API
if (!hasParams) {
methodArgs = t.assignmentPattern(methodArgs, t.objectExpression([]));
}
else if (hasParams && fieldNames.length === 1 && fieldNames.includes('pagination')) {
const paginationDefaultFromPartial = context.pluginValue('prototypes.paginationDefaultFromPartial');
// if only argument "required" is pagination
// also default to empty
methodArgs = t.assignmentPattern(methodArgs, t.objectExpression([
t.objectProperty(t.identifier('pagination'), paginationDefaultFromPartial ? t.callExpression(t.memberExpression(t.identifier("PageRequest"), t.identifier("fromPartial")), [t.objectExpression([])]) :
t.identifier('undefined'), false, false)
]));
}
const body = t.blockStatement([
// const data = QueryAccountsRequest.encode(request).finish();
encodeData(requestType),
// const promise = this.rpc.request("cosmos.auth.v1beta1.Query", "Accounts", data);
promiseRequest(msg, packageImport),
// return promise.then((data) => QueryAccountsResponse.decode(new _m0.Reader(data)));
returnPromise(responseType, context)
]);
if (context.pluginValue('classesUseArrowFunctions')) {
return classProperty(t.identifier(name), arrowFunctionExpression([methodArgs], body, returnReponseType(responseType), true), undefined, undefined, undefined, undefined, undefined, undefined, makeComment(comment));
}
return classMethod('method', t.identifier(name), [
methodArgs,
...(context.options.interfaces.enabled && context.options.interfaces.useUseInterfacesParams ? [
t.assignmentPattern(identifier('useInterfaces', t.tsTypeAnnotation(t.tsBooleanKeyword())), t.identifier((context.pluginValue('interfaces.useByDefaultRpc') ?? true).toString()))
] : []),
], body, returnReponseType(responseType));
};
const rpcTxClassMethod = (context, name, msg, svc, packageImport) => {
const requestType = svc.requestType;
const responseType = t.tsTypeReference(t.identifier("DeliverTxResponse"));
const comment = svc.comment ?? svc.name;
const methodArgs = [
identifier("signerAddress", t.tsTypeAnnotation(t.tsStringKeyword())),
identifier("message", t.tsTypeAnnotation(t.tsTypeReference(t.identifier(requestType)))),
t.assignmentPattern(identifier("fee", t.tsTypeAnnotation(t.tsUnionType([
t.tsNumberKeyword(),
t.tsTypeReference(t.identifier("StdFee")),
t.tsLiteralType(t.stringLiteral("auto")),
]))), t.stringLiteral("auto")),
t.assignmentPattern(identifier("memo", t.tsTypeAnnotation(t.tsStringKeyword())), t.stringLiteral("")),
];
const body = t.blockStatement([
// generate:
// const data = [
// {
// typeUrl: MsgCreateValidator.typeUrl,
// value: message,
// },
// ];
t.variableDeclaration('const', [
t.variableDeclarator(t.identifier('data'), t.arrayExpression([
t.objectExpression([
t.objectProperty(t.identifier('typeUrl'), t.memberExpression(t.identifier(svc.requestType), t.identifier('typeUrl'))),
t.objectProperty(t.identifier('value'), t.identifier('message'))
])
]))
]),
// generate:
// return this.rpc.signAndBroadcast!(
// signerAddress,
// data,
// fee,
// memo
// );
t.returnStatement(t.callExpression(t.memberExpression(t.memberExpression(t.thisExpression(), t.identifier('rpc')), t.identifier('signAndBroadcast!')), [
t.identifier('signerAddress'),
t.identifier('data'),
t.identifier('fee'),
t.identifier('memo')
]))
]);
if (context.pluginValue('classesUseArrowFunctions')) {
return classProperty(t.identifier(name), arrowFunctionExpression(methodArgs, body, returnReponseType(responseType), true), undefined, undefined, undefined, undefined, undefined, undefined, makeComment(comment));
}
return classMethod('method', t.identifier(name), methodArgs, body, returnReponseType(responseType));
};
const rpcClassConstructor = (context, methods) => {
const useTelescopeGeneratedType = context.pluginValue('prototypes.typingsFormat.useTelescopeGeneratedType');
let bound = [];
if (!context.pluginValue('classesUseArrowFunctions')) {
bound = methods.map(method => bindThis(method));
}
return classMethod('constructor', t.identifier('constructor'), [
identifier('rpc', t.tsTypeAnnotation(t.tsTypeReference(t.identifier(useTelescopeGeneratedType ? 'TxRpc' : 'Rpc'))))
], t.blockStatement([
t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.thisExpression(), t.identifier('rpc')), t.identifier('rpc'))),
/// methods
...bound
]));
};
export const createRpcClientInterface = (context, service, name, methodKeys, nameMapping) => {
const serviceImplement = context.pluginValue('rpcClients.serviceImplement');
const camelRpcMethods = context.pluginValue('rpcClients.camelCase');
const keys = methodKeys && methodKeys.length ? methodKeys : Object.keys(service.methods ?? {});
const methods = keys
.map((key) => {
const methodName = camelRpcMethods ? camel(key) : key;
const implementType = getServiceImplement(service.name, context.ref.proto.package, methodName, serviceImplement);
const method = service.methods[key];
if (!method) {
return null;
}
const nameWithPkg = `${context.ref.proto.package}.${methodName}`;
const methodAlias = nameMapping && nameMapping[nameWithPkg] ? nameMapping[nameWithPkg] : methodName;
const leadingComments = method.comment ? [commentBlock(processRpcComment(method))] : [];
let trailingComments = [];
switch (implementType) {
case "Tx":
context.addUtil("DeliverTxResponse");
context.addUtil("StdFee");
return rpcTxMethodDefinition(methodAlias, method, trailingComments, leadingComments);
case "Query":
default:
return rpcMethodDefinition(methodAlias, method, trailingComments, leadingComments);
}
}).filter(Boolean);
const obj = t.exportNamedDeclaration(t.tsInterfaceDeclaration(t.identifier(name ?? service.name), null, [], t.tsInterfaceBody([
...methods
])));
if (service.comment) {
obj.leadingComments = [commentBlock(`* ${service.comment} `)];
}
return obj;
};
export const getRpcClassName = (service) => {
return `${service.name}ClientImpl`;
};
export const createRpcClientClass = (context, service) => {
const serviceImplement = context.pluginValue('rpcClients.serviceImplement');
const useTelescopeGeneratedType = context.pluginValue('prototypes.typingsFormat.useTelescopeGeneratedType');
if (useTelescopeGeneratedType) {
context.addUtil('TxRpc');
}
else {
context.addUtil('Rpc');
}
BinaryCoder.addUtil(context, 'reader');
const camelRpcMethods = context.pluginValue('rpcClients.camelCase');
const name = getRpcClassName(service);
const implementsName = service.name;
const methodNames = Object.keys(service.methods ?? {})
.map(key => {
return camelRpcMethods ? camel(key) : key;
});
const methods = Object.keys(service.methods ?? {})
.map(key => {
const name = camelRpcMethods ? camel(key) : key;
const implementType = getServiceImplement(service.name, context.ref.proto.package, name, serviceImplement);
const method = service.methods[key];
switch (implementType) {
case "Tx":
context.addUtil("DeliverTxResponse");
context.addUtil("StdFee");
return rpcTxClassMethod(context, name, key, method, context.ref.proto.package + '.' + service.name);
case "Query":
default:
return rpcClassMethod(context, name, key, method, context.ref.proto.package + '.' + service.name);
}
});
return t.exportNamedDeclaration(classDeclaration(t.identifier(name), null, t.classBody([
classProperty(t.identifier('rpc'), null, t.tsTypeAnnotation(t.tsTypeReference(t.identifier(useTelescopeGeneratedType ? 'TxRpc' : 'Rpc'))), null, false, false, true, 'private'),
// CONSTRUCTOR
rpcClassConstructor(context, methodNames),
// METHODS
...methods
]), null, [
t.tsExpressionWithTypeArguments(t.identifier(implementsName))
]));
};
export const createRpcInterface = (context, service) => {
return t.tsInterfaceDeclaration(t.identifier('Rpc'), null, [], t.tsInterfaceBody([
t.tsMethodSignature(t.identifier('request'), null, [
identifier('service', t.tsTypeAnnotation(t.tsStringKeyword())),
identifier('method', t.tsTypeAnnotation(t.tsStringKeyword())),
identifier('data', t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Uint8Array'))))
], t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([
t.tsTypeReference(t.identifier('Uint8Array'))
]))))
]));
};