@cosmology/ast
Version:
Cosmos TypeScript AST generation
271 lines (270 loc) • 10.4 kB
JavaScript
import * as t from "@babel/types";
import { getAcceptedInterfacesTypes, identifier, objectMethod, } from "../../../utils";
import { getFieldOptionalityForDefaults, getOneOfs, } from "../types";
import { SymbolNames } from "../../types";
import { getTypeUrl } from "@cosmology/utils";
const INPUT_PARAM = "o";
export const createInstanceOfTypeComparison = (args) => {
const { fieldName, type } = args;
return t.binaryExpression("instanceof", fieldName, t.identifier(type));
};
export const createInstanceOfTypeComparisonGroup = (args) => {
const { context, fieldName, field, types } = args;
switch (types.length) {
case 0:
throw new Error("types shouldn't be empty.");
case 1:
return createInstanceOfTypeComparison({
context,
fieldName,
field,
type: types[0],
});
default:
const current = types.shift();
return t.logicalExpression("||", createInstanceOfTypeComparison({
context,
fieldName,
field,
type: current,
}), createInstanceOfTypeComparisonGroup({
context,
fieldName,
field,
types,
}));
}
};
export const createScalarTypeComparison = (args) => {
const { fieldName, type } = args;
return t.binaryExpression("===", t.unaryExpression("typeof", fieldName), t.stringLiteral(type));
};
export const createFieldExistingTest = (args) => {
const { fieldName } = args;
args.context.addUtil("isSet");
return t.callExpression(t.identifier("isSet"), [fieldName]);
};
export const createProtoTypeComparison = (args) => {
const { context, fieldName, field, methodName } = args;
switch (field.type) {
case "google.protobuf.Duration":
case "Duration":
const durationFormat = args.context.pluginValue("prototypes.typingsFormat.duration");
if (durationFormat === "string") {
return createScalarTypeComparison({
context,
fieldName,
field,
type: "string",
});
}
case "google.protobuf.Timestamp":
case "Timestamp":
const timestampFormat = args.context.pluginValue("prototypes.typingsFormat.timestamp");
if (timestampFormat === "date") {
createInstanceOfTypeComparison({
context,
fieldName,
field,
type: "Date",
});
}
case "Any":
case "google.protobuf.Any":
const lookupInterface = field.options?.["(cosmos_proto.accepts_interface)"];
const acceptedTypes = getAcceptedInterfacesTypes(context, lookupInterface);
const acceptedTypeNames = acceptedTypes.map((acceptedType) => acceptedType.readAs);
const typeName = context.getTypeName(field);
acceptedTypeNames.push(typeName);
return acceptedTypeNames.reduce((comparison, acceptedTypeName) => {
const current = t.callExpression(t.memberExpression(t.identifier(acceptedTypeName), t.identifier(methodName)), [fieldName]);
return comparison
? t.logicalExpression("||", comparison, current)
: current;
}, undefined);
}
const typeName = context.getTypeName(field);
return t.callExpression(t.memberExpression(t.identifier(typeName), t.identifier(methodName)), [fieldName]);
};
export const createArrayTypeComparison = (args) => {
const { fieldName, typeComparison } = args;
const isArrayExp = t.callExpression(t.memberExpression(t.identifier("Array"), t.identifier("isArray")), [t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName))]);
return typeComparison
? t.logicalExpression("&&", isArrayExp, t.logicalExpression("||", t.unaryExpression("!", t.memberExpression(t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName)), t.identifier("length"))), typeComparison))
: isArrayExp;
};
function getScalarExpression(args) {
//TODO:: Date, timestamp, etc
const { context, field, fieldName } = args;
switch (field.type) {
case "string":
return createScalarTypeComparison({
context,
field,
fieldName,
type: "string",
});
case "bool":
return createScalarTypeComparison({
context,
field,
fieldName,
type: "boolean",
});
case "float":
case "double":
case "int32":
case "sint32":
case "uint32":
case "fixed32":
case "sfixed32":
return createScalarTypeComparison({
context,
field,
fieldName,
type: "number",
});
case "bytes":
return t.logicalExpression("||", createInstanceOfTypeComparison({
context,
field,
fieldName,
type: "Uint8Array",
}), createScalarTypeComparison({
context,
field,
fieldName,
type: "string",
}));
case "int64":
case "sint64":
case "uint64":
case "fixed64":
case "sfixed64":
return createScalarTypeComparison({
context,
field,
fieldName,
type: "bigint",
});
}
return undefined;
}
export const createFieldTypeComparison = (args) => {
const { context, field, fieldName, methodName } = args;
if (field.keyType) {
return createFieldExistingTest({
context,
fieldName: t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName)),
field,
});
}
switch (field.parsedType.type) {
case "Enum":
if (field.rule === "repeated") {
return createArrayTypeComparison({
context,
field,
fieldName,
});
}
else {
return createFieldExistingTest({
context,
fieldName: t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName)),
field,
});
}
case "Type":
if (field.rule === "repeated") {
return createArrayTypeComparison({
context,
field,
fieldName,
typeComparison: createProtoTypeComparison({
context,
methodName,
field,
fieldName: t.memberExpression(t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName)), t.numericLiteral(0), true),
}),
});
}
else {
return createProtoTypeComparison({
context,
methodName,
fieldName: t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName)),
field,
});
}
}
if (field.rule === "repeated") {
return createArrayTypeComparison({
context,
field,
fieldName,
typeComparison: getScalarExpression({
context,
field,
fieldName: t.memberExpression(t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName)), t.numericLiteral(0), true),
}),
});
}
const expr = getScalarExpression({
context,
field,
fieldName: t.memberExpression(t.identifier(INPUT_PARAM), t.identifier(fieldName)),
});
if (expr) {
return expr;
}
throw new Error(`need to implement is (${field.type} rules[${field.rule}] name[${fieldName}])`);
};
export const isMethod = (args) => {
const { context, name, proto, getFieldName } = args;
const methodName = args.methodName ?? "is";
const typeName = getTypeName(name, methodName);
const typeUrl = getTypeUrl(context.ref.proto, proto);
if (!typeUrl)
return;
const returnType = t.tsTypeAnnotation(t.tsTypePredicate(t.identifier(INPUT_PARAM), t.tsTypeAnnotation(t.tsTypeReference(t.identifier(typeName)))));
const fieldTypesComparison = Object.keys(proto.fields ?? {}).reduce((comparison, fieldName) => {
const field = proto.fields[fieldName];
const oneOfs = getOneOfs(proto);
const isOneOf = oneOfs.includes(fieldName);
const isOptional = getFieldOptionalityForDefaults(context, field, isOneOf);
if (isOptional) {
return comparison;
}
const fieldNameWithCase = getFieldName
? getFieldName(fieldName, field, name, context)
: fieldName;
const current = createFieldTypeComparison({
context,
methodName,
field,
fieldName: fieldNameWithCase,
});
return comparison
? t.logicalExpression("&&", comparison, current)
: current;
}, undefined);
const typeUrlExpr = t.binaryExpression("===", t.memberExpression(t.identifier(INPUT_PARAM), t.identifier("$typeUrl")), t.memberExpression(t.identifier(name), t.identifier("typeUrl")));
const method = objectMethod("method", t.identifier(methodName), [identifier(INPUT_PARAM, t.tsTypeAnnotation(t.tsAnyKeyword()), false)], t.blockStatement([
t.returnStatement(t.logicalExpression("&&", t.identifier(INPUT_PARAM), fieldTypesComparison
? t.logicalExpression("||", typeUrlExpr, fieldTypesComparison)
: typeUrlExpr)),
]), false, false, false, returnType);
return method;
};
function getTypeName(typeName, methodName) {
switch (methodName) {
case "isSDK":
return SymbolNames.SDKType(typeName);
case "isAmino":
return SymbolNames.Amino(typeName);
case "is":
default:
return typeName;
}
}