protoc-gen-ts-alt
Version:
Generate d.ts definitions for generated js files from grpc_tools_node_protoc
284 lines (244 loc) • 11.7 kB
text/typescript
import {
DescriptorProto,
FieldDescriptorProto,
FileDescriptorProto,
OneofDescriptorProto
} from "google-protobuf/google/protobuf/descriptor_pb";
import {ExportMap} from "../../ExportMap";
import {Utility} from "../../Utility";
import {BYTES_TYPE, ENUM_TYPE, FieldTypesFormatter, MESSAGE_TYPE} from "./FieldTypesFormatter";
import {EnumFormatter} from "./EnumFormatter";
import {ExtensionFormatter} from "./ExtensionFormatter";
import {OneofFormatter} from "./OneofFormatter";
import {TplEngine} from "../../TplEngine";
export const OBJECT_TYPE_NAME = 'AsObject';
export namespace MessageFormatter {
export interface MessageType {
messageName: string;
oneofGroups: Array<Array<FieldDescriptorProto>>;
oneofDeclList: Array<OneofDescriptorProto>;
fields: Array<MessageFieldType>;
nestedTypes: Array<MessageFormatter.MessageModel>;
formattedEnumListStr: Array<EnumFormatter.EnumModel>;
formattedOneofListStr: Array<OneofFormatter.OneofModel>;
formattedExtListStr: Array<ExtensionFormatter.ExtensionModel>;
}
export const defaultMessageType = JSON.stringify({
messageName: "",
oneofGroups: [],
oneofDeclList: [],
fields: [],
nestedTypes: [],
formattedEnumListStr: [],
formattedOneofListStr: [],
formattedExtListStr: [],
} as MessageType);
export interface MessageFieldType {
snakeCaseName: string;
camelCaseName: string;
camelUpperName: string;
fieldObjectType: string;
type: FieldDescriptorProto.Type;
exportType: string;
isMapField: boolean;
mapFieldInfo?: MessageMapField;
isRepeatField: boolean;
isOptionalValue: boolean;
canBeUndefined: boolean;
hasClearMethodCreated: boolean;
hasFieldPresence: boolean;
}
export const defaultMessageFieldType = JSON.stringify({
snakeCaseName: "",
camelCaseName: "",
camelUpperName: "",
fieldObjectType: "",
type: undefined,
exportType: "",
isMapField: false,
mapFieldInfo: undefined,
isRepeatField: false,
isOptionalValue: false,
canBeUndefined: false,
hasClearMethodCreated: false,
hasFieldPresence: false,
} as MessageFieldType);
export interface MessageMapField {
keyType: FieldDescriptorProto.Type;
keyTypeName: string;
valueType: FieldDescriptorProto.Type;
valueTypeName: string;
}
export interface MessageModel {
indent: string;
objectTypeName: string;
BYTES_TYPE: number;
MESSAGE_TYPE: number;
message: MessageType;
}
function hasFieldPresence(field: FieldDescriptorProto, descriptor: FileDescriptorProto): boolean {
if (field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED) {
return false;
}
if (field.hasOneofIndex()) {
return true;
}
if (field.getType() === MESSAGE_TYPE) {
return true;
}
return Utility.isProto2(descriptor);
}
export function format(fileName: string,
exportMap: ExportMap,
descriptor: DescriptorProto,
indent: string,
fileDescriptor: FileDescriptorProto): MessageModel {
const nextIndent = `${indent} `;
let messageData = JSON.parse(defaultMessageType) as MessageType;
messageData.messageName = descriptor.getName();
messageData.oneofDeclList = descriptor.getOneofDeclList();
let messageOptions = descriptor.getOptions();
if (messageOptions !== undefined && messageOptions.getMapEntry()) {
// this message type is the entry tuple for a map - don't output it
return null;
}
let oneofGroups: Array<Array<FieldDescriptorProto>> = [];
descriptor.getFieldList().forEach((field: FieldDescriptorProto) => {
let fieldData = JSON.parse(defaultMessageFieldType) as MessageFieldType;
if (field.hasOneofIndex()) {
let oneOfIndex = field.getOneofIndex();
let existing = oneofGroups[oneOfIndex];
if (existing === undefined) {
existing = [];
oneofGroups[oneOfIndex] = existing;
}
existing.push(field);
}
fieldData.snakeCaseName = field.getName().toLowerCase();
fieldData.camelCaseName = Utility.snakeToCamel(fieldData.snakeCaseName);
fieldData.camelUpperName = Utility.uppercaseFirst(fieldData.camelCaseName);
// handle reserved keywords in field names like Javascript generator
// see: https://github.com/google/protobuf/blob/ed4321d1cb33199984118d801956822842771e7e/src/google/protobuf/compiler/js/js_generator.cc#L508-L510
if (Utility.isReserved(fieldData.camelCaseName)) {
fieldData.camelCaseName = `pb_${fieldData.camelCaseName}`;
}
fieldData.type = field.getType();
fieldData.isMapField = false;
fieldData.canBeUndefined = false;
let exportType;
let fullTypeName = field.getTypeName().slice(1);
if (fieldData.type === MESSAGE_TYPE) {
const fieldMessageType = exportMap.getMessage(fullTypeName);
if (fieldMessageType === undefined) {
throw new Error("No message export for: " + fullTypeName);
}
fieldData.isMapField = fieldMessageType.messageOptions !== undefined
&& fieldMessageType.messageOptions.getMapEntry();
if (fieldData.isMapField) {
let mapData = {} as MessageMapField;
let keyTuple = fieldMessageType.mapFieldOptions!.key;
let keyType = keyTuple[0];
let keyTypeName = FieldTypesFormatter.getFieldType(keyType, keyTuple[1] as string, fileName, exportMap);
let valueTuple = fieldMessageType.mapFieldOptions!.value;
let valueType = valueTuple[0];
let valueTypeName = FieldTypesFormatter.getFieldType(valueType, valueTuple[1] as string, fileName, exportMap);
if (valueType === BYTES_TYPE) {
valueTypeName = "Uint8Array | string";
}
mapData.keyType = keyType;
mapData.keyTypeName = keyTypeName;
mapData.valueType = valueType;
mapData.valueTypeName = valueTypeName;
fieldData.mapFieldInfo = mapData;
messageData.fields.push(fieldData);
return;
}
let withinNamespace = Utility.withinNamespaceFromExportEntry(fullTypeName, fieldMessageType);
if (fieldMessageType.fileName === fileName) {
exportType = withinNamespace;
} else {
exportType = Utility.filePathToPseudoNamespace(fieldMessageType.fileName) + "." + withinNamespace;
}
fieldData.exportType = exportType;
} else if (fieldData.type === ENUM_TYPE) {
let fieldEnumType = exportMap.getEnum(fullTypeName);
if (fieldEnumType === undefined) {
throw new Error("No enum export for: " + fullTypeName);
}
let withinNamespace = Utility.withinNamespaceFromExportEntry(fullTypeName, fieldEnumType);
if (fieldEnumType.fileName === fileName) {
exportType = withinNamespace;
} else {
exportType = Utility.filePathToPseudoNamespace(fieldEnumType.fileName) + "." + withinNamespace;
}
fieldData.exportType = exportType;
} else {
let type = FieldTypesFormatter.getTypeName(fieldData.type);
// Check for [jstype = JS_STRING] overrides
const options = field.getOptions();
if (options && options.hasJstype()) {
const jstype = FieldTypesFormatter.getJsTypeName(options.getJstype());
if (jstype) {
type = jstype;
}
}
exportType = fieldData.exportType = type;
}
fieldData.isOptionalValue = field.getType() === MESSAGE_TYPE;
fieldData.isRepeatField = field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED;
if (!fieldData.isRepeatField && fieldData.type !== BYTES_TYPE) {
let fieldObjectType = exportType;
let canBeUndefined = false;
if (fieldData.type === MESSAGE_TYPE) {
fieldObjectType += ".AsObject";
if (!Utility.isProto2(fileDescriptor) || (field.getLabel() === FieldDescriptorProto.Label.LABEL_OPTIONAL)) {
canBeUndefined = true;
}
} else {
if (Utility.isProto2(fileDescriptor)) {
canBeUndefined = true;
}
}
fieldData.fieldObjectType = fieldObjectType;
fieldData.canBeUndefined = canBeUndefined;
}
fieldData.hasFieldPresence = hasFieldPresence(field, fileDescriptor);
messageData.fields.push(fieldData);
});
descriptor.getNestedTypeList().forEach(nested => {
const msgOutput = format(fileName, exportMap, nested, nextIndent, fileDescriptor);
if (msgOutput !== null) {
// If the message class is a Map entry then it isn't output, so don't print the namespace block
messageData.nestedTypes.push(msgOutput);
}
});
descriptor.getEnumTypeList().forEach(enumType => {
messageData.formattedEnumListStr.push(EnumFormatter.format(enumType, nextIndent));
});
descriptor.getOneofDeclList().forEach((oneOfDecl, index) => {
messageData.formattedOneofListStr.push(OneofFormatter.format(oneOfDecl, oneofGroups[index] || [], nextIndent));
});
descriptor.getExtensionList().forEach(extension => {
messageData.formattedExtListStr.push(ExtensionFormatter.format(fileName, exportMap, extension, nextIndent));
});
TplEngine.registerHelper('printClearIfNotPresent', function (fieldData: MessageFieldType) {
if (!fieldData.hasClearMethodCreated) {
fieldData.hasClearMethodCreated = true;
return `clear${fieldData.camelUpperName}${fieldData.isRepeatField ? "List" : ""}(): void;`;
}
});
TplEngine.registerHelper('printRepeatedAddMethod', function (fieldData: MessageFieldType, valueType: string) {
return `add${fieldData.camelUpperName}(value${fieldData.isOptionalValue ? "?" : ""}: ${valueType}, index?: number): ${valueType};`;
});
TplEngine.registerHelper('oneOfName', function (oneOfDecl: OneofDescriptorProto) {
return Utility.oneOfName(oneOfDecl.getName());
});
return {
indent,
objectTypeName: OBJECT_TYPE_NAME,
BYTES_TYPE: BYTES_TYPE,
MESSAGE_TYPE: MESSAGE_TYPE,
message: messageData,
};
}
}