@tingyutech/protobuf-ts-plugin
Version:
The protocol buffer compiler plugin "protobuf-ts" generates TypeScript, gRPC-web, Twirp, and more.
222 lines (221 loc) • 10.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MessageInterfaceGenerator = void 0;
const ts = require("typescript");
const rt = require("@protobuf-ts/runtime");
const runtime_1 = require("@protobuf-ts/runtime");
const plugin_framework_1 = require("@protobuf-ts/plugin-framework");
const generator_base_1 = require("./generator-base");
const local_type_name_1 = require("./local-type-name");
class MessageInterfaceGenerator extends generator_base_1.GeneratorBase {
constructor(symbols, registry, imports, comments, interpreter, options) {
super(symbols, registry, imports, comments, interpreter);
this.options = options;
}
registerSymbols(source, descriptor) {
const name = local_type_name_1.createLocalTypeName(descriptor, this.registry);
this.symbols.register(name, descriptor, source);
}
/**
* `message` as an interface.
*
* For the following .proto:
*
* message MyMessage {
* string str_field = 1;
* }
*
* We generate the following interface:
*
* interface MyMessage {
* strField: string;
* }
*
*/
generateMessageInterface(source, descriptor) {
const interpreterType = this.interpreter.getMessageType(descriptor), processedOneofs = [], // oneof groups already processed
members = []; // the interface members
for (let fieldInfo of interpreterType.fields) {
let fieldDescriptor = descriptor.field.find(fd => fd.number === fieldInfo.no);
runtime_1.assert(fieldDescriptor !== undefined);
if (fieldInfo.oneof) {
if (processedOneofs.includes(fieldInfo.oneof)) {
continue;
}
// create single property for entire oneof group
runtime_1.assert(fieldDescriptor.oneofIndex !== undefined);
let oneofDescriptor = descriptor.oneofDecl[fieldDescriptor.oneofIndex];
runtime_1.assert(oneofDescriptor !== undefined);
members.push(this.createOneofADTPropertySignature(source, oneofDescriptor));
processedOneofs.push(fieldInfo.oneof);
}
else {
// create regular properties
members.push(this.createFieldPropertySignature(source, fieldDescriptor, fieldInfo));
}
}
// export interface MyMessage { ...
const statement = ts.createInterfaceDeclaration(undefined, [ts.createModifier(ts.SyntaxKind.ExportKeyword)], this.imports.type(source, descriptor), undefined, undefined, members);
// add to our file
source.addStatement(statement);
this.comments.addCommentsForDescriptor(statement, descriptor, 'appendToLeadingBlock');
return statement;
}
/**
* Create property signature for a protobuf field. Example:
*
* fieldName: number
*
*/
createFieldPropertySignature(source, fieldDescriptor, fieldInfo) {
let type; // the property type, may be made optional or wrapped into array at the end
switch (fieldInfo.kind) {
case "scalar":
type = this.createScalarTypeNode(fieldInfo.T, fieldInfo.L);
break;
case "enum":
type = this.createEnumTypeNode(source, fieldInfo.T());
break;
case "message":
type = this.createMessageTypeNode(source, fieldInfo.T());
break;
case "map":
let keyType = fieldInfo.K === rt.ScalarType.BOOL
? ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
: this.createScalarTypeNode(fieldInfo.K, rt.LongType.STRING);
let valueType;
switch (fieldInfo.V.kind) {
case "scalar":
valueType = this.createScalarTypeNode(fieldInfo.V.T, fieldInfo.V.L);
break;
case "enum":
valueType = this.createEnumTypeNode(source, fieldInfo.V.T());
break;
case "message":
valueType = this.createMessageTypeNode(source, fieldInfo.V.T());
break;
}
type = ts.createTypeLiteralNode([
ts.createIndexSignature(undefined, undefined, [
ts.createParameter(undefined, undefined, undefined, ts.createIdentifier('key'), undefined, keyType, undefined)
], valueType)
]);
break;
default:
throw new Error("unkown kind " + fieldDescriptor.name);
}
// if repeated, wrap type into array type
if (fieldInfo.repeat) {
type = ts.createArrayTypeNode(type);
}
// if optional, add question mark
let questionToken = fieldInfo.opt ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined;
// create property
const property = ts.createPropertySignature(undefined, ts.createIdentifier(fieldInfo.localName), questionToken, type, undefined);
this.comments.addCommentsForDescriptor(property, fieldDescriptor, 'trailingLines');
return property;
}
/**
* `oneof` as an algebraic data type.
*
* For the following .proto:
*
* oneof result {
* int32 value = 1;
* string error = 2;
* }
*
* We generate the following property signature:
*
* result: { oneofKind: "value"; value: number; }
* | { oneofKind: "error"; error: string; }
* | { oneofKind: undefined; };
*/
createOneofADTPropertySignature(source, oneofDescriptor) {
const oneofCases = [], [messageDescriptor, interpreterType, oneofLocalName] = this.oneofInfo(oneofDescriptor), memberFieldInfos = interpreterType.fields.filter(fi => fi.oneof === oneofLocalName);
runtime_1.assert(oneofDescriptor !== undefined);
// create a type for each selection case
for (let fieldInfo of memberFieldInfos) {
// { oneofKind: 'fieldName' ... } part
const kindProperty = ts.createPropertySignature(undefined, ts.createIdentifier(this.options.oneofKindDiscriminator), undefined, ts.createLiteralTypeNode(ts.createStringLiteral(fieldInfo.localName)), undefined);
// { ..., fieldName: type } part
let fieldDescriptor = messageDescriptor.field.find(fd => fd.number === fieldInfo.no);
runtime_1.assert(fieldDescriptor !== undefined);
let valueProperty = this.createFieldPropertySignature(source, fieldDescriptor, fieldInfo);
// add this case
oneofCases.push(ts.createTypeLiteralNode([kindProperty, valueProperty]));
}
// case for no selection: { oneofKind: undefined; }
oneofCases.push(ts.createTypeLiteralNode([
ts.createPropertySignature(undefined, ts.createIdentifier(this.options.oneofKindDiscriminator), undefined, ts.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword), undefined)
]));
// final property signature for the oneof group, with a union type for all oneof cases
const property = ts.createPropertySignature(undefined, ts.createIdentifier(oneofLocalName), undefined, ts.createUnionTypeNode(oneofCases), undefined);
// add comments
this.comments.addCommentsForDescriptor(property, oneofDescriptor, 'appendToLeadingBlock');
return property;
}
/**
* Helper to find for a OneofDescriptorProto:
* [0] the message descriptor
* [1] a corresponding message type generated by the interpreter
* [2] the runtime local name of the oneof
*/
oneofInfo(oneofDescriptor) {
const messageDescriptor = this.registry.parentOf(oneofDescriptor);
runtime_1.assert(plugin_framework_1.DescriptorProto.is(messageDescriptor));
const interpreterType = this.interpreter.getMessageType(messageDescriptor);
const oneofIndex = messageDescriptor.oneofDecl.indexOf(oneofDescriptor);
runtime_1.assert(oneofIndex !== undefined);
const sampleFieldDescriptor = messageDescriptor.field.find(fd => fd.oneofIndex === oneofIndex);
runtime_1.assert(sampleFieldDescriptor !== undefined);
const sampleFieldInfo = interpreterType.fields.find(fi => fi.no === sampleFieldDescriptor.number);
runtime_1.assert(sampleFieldInfo !== undefined);
const oneofName = sampleFieldInfo.oneof;
runtime_1.assert(oneofName !== undefined);
return [messageDescriptor, interpreterType, oneofName];
}
createScalarTypeNode(scalarType, longType) {
switch (scalarType) {
case rt.ScalarType.BOOL:
return ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
case rt.ScalarType.STRING:
return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case rt.ScalarType.BYTES:
return ts.createTypeReferenceNode('Uint8Array', undefined);
case rt.ScalarType.DOUBLE:
case rt.ScalarType.FLOAT:
case rt.ScalarType.INT32:
case rt.ScalarType.FIXED32:
case rt.ScalarType.UINT32:
case rt.ScalarType.SFIXED32:
case rt.ScalarType.SINT32:
return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
case rt.ScalarType.SFIXED64:
case rt.ScalarType.INT64:
case rt.ScalarType.UINT64:
case rt.ScalarType.FIXED64:
case rt.ScalarType.SINT64:
switch (longType !== null && longType !== void 0 ? longType : rt.LongType.STRING) {
case rt.LongType.STRING:
return ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
case rt.LongType.NUMBER:
return ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
case rt.LongType.BIGINT:
return ts.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword);
}
}
}
createMessageTypeNode(source, type) {
let messageDescriptor = this.registry.resolveTypeName(type.typeName);
runtime_1.assert(plugin_framework_1.DescriptorProto.is(messageDescriptor));
return ts.createTypeReferenceNode(this.imports.type(source, messageDescriptor), undefined);
}
createEnumTypeNode(source, ei) {
let [enumTypeName] = ei;
let enumDescriptor = this.registry.resolveTypeName(enumTypeName);
runtime_1.assert(plugin_framework_1.EnumDescriptorProto.is(enumDescriptor));
return ts.createTypeReferenceNode(this.imports.type(source, enumDescriptor), undefined);
}
}
exports.MessageInterfaceGenerator = MessageInterfaceGenerator;