UNPKG

grpc_tools_node_protoc_ts

Version:

Generate d.ts definitions for generated js files from grpc_tools_node_protoc

310 lines (267 loc) 12.7 kB
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 IMessageType { messageName: string; oneofGroups: FieldDescriptorProto[][]; oneofDeclList: OneofDescriptorProto[]; fields: IMessageFieldType[]; nestedTypes: MessageFormatter.IMessageModel[]; formattedEnumListStr: EnumFormatter.IEnumModel[]; formattedOneofListStr: OneofFormatter.IOneofModel[]; formattedExtListStr: ExtensionFormatter.IExtensionModel[]; } export const defaultMessageType = JSON.stringify({ messageName: "", oneofGroups: [], oneofDeclList: [], fields: [], nestedTypes: [], formattedEnumListStr: [], formattedOneofListStr: [], formattedExtListStr: [], } as IMessageType); export interface IMessageFieldType { snakeCaseName: string; camelCaseName: string; camelUpperName: string; fieldObjectType: string; type: FieldDescriptorProto.Type; exportType: string; isMapField: boolean; mapFieldInfo?: IMessageMapField; 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 IMessageFieldType); export interface IMessageMapField { keyType: FieldDescriptorProto.Type; keyTypeName: string; valueType: FieldDescriptorProto.Type; valueTypeName: string; } export interface IMessageModel { indent: string; objectTypeName: string; BYTES_TYPE: number; MESSAGE_TYPE: number; message: IMessageType; } function hasFieldPresence(field: FieldDescriptorProto, descriptor: FileDescriptorProto): boolean { if (field.getLabel() === FieldDescriptorProto.Label.LABEL_REPEATED) { return false; } if (field.hasProto3Optional()) { return true; } 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): IMessageModel { const nextIndent = `${indent} `; const messageData = JSON.parse(defaultMessageType) as IMessageType; const proto3OptionalFields = new Set<string>(); descriptor.getFieldList().forEach((field) => { if (field.hasName() && field.hasProto3Optional()) { proto3OptionalFields.add(field.getName()); } }); messageData.messageName = descriptor.getName(); messageData.oneofDeclList = descriptor.getOneofDeclList().filter((oneOfDecl) => { const name = oneOfDecl.getName(); return !(name && name.length > 1 && proto3OptionalFields.has(name.substring(1))); }); const messageOptions = descriptor.getOptions(); if (messageOptions !== undefined && messageOptions.getMapEntry()) { // this message type is the entry tuple for a map - don't output it return null; } const oneofGroups: FieldDescriptorProto[][] = []; descriptor.getFieldList().forEach((field: FieldDescriptorProto) => { const fieldData = JSON.parse(defaultMessageFieldType) as IMessageFieldType; if (field.hasOneofIndex()) { const 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; const 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) { const mapData = {} as IMessageMapField; const keyTuple = fieldMessageType.mapFieldOptions!.key; const keyType = keyTuple[0]; const keyTypeName = FieldTypesFormatter.getFieldType(keyType, keyTuple[1] as string, fileName, exportMap); const valueTuple = fieldMessageType.mapFieldOptions!.value; const 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; } const 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) { const fieldEnumType = exportMap.getEnum(fullTypeName); if (fieldEnumType === undefined) { throw new Error("No enum export for: " + fullTypeName); } const 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 (field.getProto3Optional()) { 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) => { const name = oneOfDecl.getName(); if (name && name.length > 1 && proto3OptionalFields.has(name.substring(1))) { // Skip synthetic one-ofs for proto3 optional fields return; } 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", (fieldData: IMessageFieldType) => { if (!fieldData.hasClearMethodCreated) { fieldData.hasClearMethodCreated = true; if (fieldData.isRepeatField) { return `clear${fieldData.camelUpperName}List(): void;`; } else { return `clear${Utility.formatOccupiedName(fieldData.camelUpperName)}(): void;`; } } }); TplEngine.registerHelper("printRepeatedAddMethod", (fieldData: IMessageFieldType, valueType: string) => { return `add${Utility.formatOccupiedName(fieldData.camelUpperName)}(value${fieldData.isOptionalValue ? "?" : ""}: ${valueType}, index?: number): ${valueType};`; }); TplEngine.registerHelper("oneOfName", (oneOfDecl: OneofDescriptorProto) => { return Utility.oneOfName(oneOfDecl.getName()); }); return { indent, objectTypeName: OBJECT_TYPE_NAME, BYTES_TYPE, MESSAGE_TYPE, message: messageData, }; } }