UNPKG

@protobuf-ts/plugin

Version:

The protocol buffer compiler plugin "protobuf-ts" generates TypeScript, gRPC-web, Twirp, and more.

348 lines (347 loc) 23.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InternalBinaryWrite = void 0; const ts = require("typescript"); const plugin_framework_1 = require("@protobuf-ts/plugin-framework"); const rt = require("@protobuf-ts/runtime"); const runtime_1 = require("@protobuf-ts/runtime"); /** * Generates the `internalBinaryWrite` method, which writes a message * in binary format. * * Heads up: The generated code is only very marginally faster than * the reflection-based one. The gain is less than 3%. * */ class InternalBinaryWrite { constructor(registry, imports, interpreter, options) { this.registry = registry; this.imports = imports; this.interpreter = interpreter; this.options = options; } make(source, descriptor) { // internalBinaryWrite(message: ScalarValuesMessage, writer: IBinaryWriter, options: BinaryWriteOptions): void { let internalBinaryWrite = this.makeMethod(source, descriptor, [ ...this.makeStatementsForEveryField(source, descriptor), ...this.makeUnknownFieldsHandler(source), // return writer; ts.createReturn(ts.createIdentifier("writer")) ]); return [internalBinaryWrite]; } makeMethod(source, descriptor, bodyStatements) { const MessageInterface = this.imports.type(source, descriptor), IBinaryWriter = this.imports.name(source, 'IBinaryWriter', this.options.runtimeImportPath, true), BinaryWriteOptions = this.imports.name(source, 'BinaryWriteOptions', this.options.runtimeImportPath, true); return ts.createMethod(undefined, undefined, undefined, ts.createIdentifier("internalBinaryWrite"), undefined, undefined, [ ts.createParameter(undefined, undefined, undefined, ts.createIdentifier("message"), undefined, ts.createTypeReferenceNode(MessageInterface, undefined), undefined), ts.createParameter(undefined, undefined, undefined, ts.createIdentifier("writer"), undefined, ts.createTypeReferenceNode(IBinaryWriter, undefined), undefined), ts.createParameter(undefined, undefined, undefined, ts.createIdentifier("options"), undefined, ts.createTypeReferenceNode(BinaryWriteOptions, undefined), undefined), ], ts.createTypeReferenceNode(IBinaryWriter, undefined), ts.createBlock(bodyStatements, true)); } makeUnknownFieldsHandler(source) { let UnknownFieldHandler = this.imports.name(source, 'UnknownFieldHandler', this.options.runtimeImportPath); return [ ts.createVariableStatement(undefined, ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createIdentifier("u"), undefined, ts.createPropertyAccess(ts.createIdentifier("options"), ts.createIdentifier("writeUnknownFields")))], ts.NodeFlags.Let)), ts.createIf(ts.createBinary(ts.createIdentifier("u"), ts.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), ts.createFalse()), ts.createExpressionStatement(ts.createCall(ts.createParen(ts.createConditional(ts.createBinary(ts.createIdentifier("u"), ts.createToken(ts.SyntaxKind.EqualsEqualsToken), ts.createTrue()), ts.createPropertyAccess(ts.createIdentifier(UnknownFieldHandler), ts.createIdentifier("onWrite")), ts.createIdentifier("u"))), undefined, [ ts.createPropertyAccess(ts.createThis(), ts.createIdentifier("typeName")), ts.createIdentifier("message"), ts.createIdentifier("writer") ])), undefined) ]; } makeStatementsForEveryField(source, descriptor) { const interpreterType = this.interpreter.getMessageType(descriptor), statements = []; for (let fieldInfo of interpreterType.fields) { let fieldDescriptor = descriptor.field.find(fd => fd.number === fieldInfo.no); runtime_1.assert(fieldDescriptor !== undefined); let fieldDeclarationComment = this.registry.formatFieldDeclaration(fieldDescriptor); let fieldPropertyAccess = ts.createPropertyAccess(ts.createIdentifier("message"), fieldInfo.localName); switch (fieldInfo.kind) { case "scalar": case "enum": if (fieldInfo.repeat) { statements.push(...this.scalarRepeated(source, fieldInfo, fieldPropertyAccess, fieldDeclarationComment)); } else if (fieldInfo.oneof !== undefined) { statements.push(...this.scalarOneof(source, fieldInfo, fieldDeclarationComment)); } else { statements.push(...this.scalar(source, fieldInfo, fieldPropertyAccess, fieldDeclarationComment)); } break; case "message": if (fieldInfo.repeat) { statements.push(...this.messageRepeated(source, fieldInfo, fieldPropertyAccess, fieldDeclarationComment)); } else if (fieldInfo.oneof !== undefined) { statements.push(...this.messageOneof(source, fieldInfo, fieldDeclarationComment)); } else { statements.push(...this.message(source, fieldInfo, fieldPropertyAccess, fieldDeclarationComment)); } break; case "map": statements.push(...this.map(source, fieldInfo, fieldPropertyAccess, fieldDeclarationComment)); break; } } return statements; } scalar(source, field, fieldPropertyAccess, fieldDeclarationComment) { let type = field.kind == "enum" ? rt.ScalarType.INT32 : field.T; // we only write scalar fields if they have a non-default value // this is the condition: let shouldWriteCondition; if (field.T === rt.ScalarType.BYTES && field.opt) { // message.bytes !== undefined shouldWriteCondition = ts.createBinary(fieldPropertyAccess, ts.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), ts.createIdentifier("undefined")); } else if (field.T === rt.ScalarType.BYTES && !field.opt) { // message.bytes.length shouldWriteCondition = ts.createPropertyAccess(fieldPropertyAccess, ts.createIdentifier("length")); } else { // message.field !== <default value> // get a default value for the scalar field using the MessageType let defaultValue = new rt.MessageType("$synthetic.InternalBinaryWrite", [field]).create()[field.localName]; let defaultValueExpression = plugin_framework_1.typescriptLiteralFromValue(defaultValue); shouldWriteCondition = ts.createBinary(fieldPropertyAccess, ts.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), defaultValueExpression); } // if ( <shouldWriteCondition> ) let statement = ts.createIf(shouldWriteCondition, // writer.tag( <field no>, <wire type> ).string(message.stringField) ts.createExpressionStatement(this.makeWriterCall(this.makeWriterTagCall(source, 'writer', field.no, this.wireTypeForSingleScalar(type)), type, fieldPropertyAccess)), undefined); ts.addSyntheticLeadingComment(statement, ts.SyntaxKind.MultiLineCommentTrivia, ' ' + fieldDeclarationComment + ' ', true); return [statement]; } scalarRepeated(source, field, fieldPropertyAccess, fieldDeclarationComment) { let statement; let type = field.kind == "enum" ? rt.ScalarType.INT32 : field.T; if (field.repeat === rt.RepeatType.PACKED) { // if (message.int32Field.length) { statement = ts.createIf(ts.createPropertyAccess(fieldPropertyAccess, ts.createIdentifier("length")), ts.createBlock([ // writer.tag(3, WireType.LengthDelimited).fork(); ts.createExpressionStatement(this.makeWriterCall(this.makeWriterTagCall(source, 'writer', field.no, rt.WireType.LengthDelimited), 'fork')), // for (let i = 0; i < message.int32Field.length; i++) ts.createFor(ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createIdentifier("i"), undefined, ts.createNumericLiteral("0"))], ts.NodeFlags.Let), ts.createBinary(ts.createIdentifier("i"), ts.createToken(ts.SyntaxKind.LessThanToken), ts.createPropertyAccess(fieldPropertyAccess, ts.createIdentifier("length"))), ts.createPostfix(ts.createIdentifier("i"), ts.SyntaxKind.PlusPlusToken), // writer.int32(message.int32Field[i]); ts.createExpressionStatement(this.makeWriterCall('writer', type, ts.createElementAccess(fieldPropertyAccess, ts.createIdentifier("i"))))), // writer.join(); ts.createExpressionStatement(this.makeWriterCall('writer', 'join')), ], true), undefined); } else { // never packed // for (let i = 0; i < message.bytesField.length; i++) statement = ts.createFor(ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createIdentifier("i"), undefined, ts.createNumericLiteral("0"))], ts.NodeFlags.Let), ts.createBinary(ts.createIdentifier("i"), ts.createToken(ts.SyntaxKind.LessThanToken), ts.createPropertyAccess(fieldPropertyAccess, ts.createIdentifier("length"))), ts.createPostfix(ts.createIdentifier("i"), ts.SyntaxKind.PlusPlusToken), // writer.tag( <field number>, <wire type> ).bytes( message.bytesField[i] ) ts.createExpressionStatement(this.makeWriterCall(this.makeWriterTagCall(source, "writer", field.no, this.wireTypeForSingleScalar(type)), type, ts.createElementAccess(fieldPropertyAccess, ts.createIdentifier("i"))))); } ts.addSyntheticLeadingComment(statement, ts.SyntaxKind.MultiLineCommentTrivia, ' ' + fieldDeclarationComment + ' ', true); return [statement]; } scalarOneof(source, field, fieldDeclarationComment) { let type = field.kind == "enum" ? rt.ScalarType.INT32 : field.T; let groupPropertyAccess = ts.createPropertyAccess(ts.createIdentifier("message"), ts.createIdentifier(field.oneof)); let statement = ts.createIf( // if (message.result.oneofKind === 'value') ts.createBinary(ts.createPropertyAccess(groupPropertyAccess, ts.createIdentifier(this.options.oneofKindDiscriminator)), ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.createStringLiteral(field.localName)), // writer.tag( <field no>, <wire type> ).string(message.stringField) ts.createExpressionStatement(this.makeWriterCall(this.makeWriterTagCall(source, 'writer', field.no, this.wireTypeForSingleScalar(type)), type, ts.createPropertyAccess(groupPropertyAccess, field.localName))), undefined); ts.addSyntheticLeadingComment(statement, ts.SyntaxKind.MultiLineCommentTrivia, ' ' + fieldDeclarationComment + ' ', true); return [statement]; } message(source, field, fieldPropertyAccess, fieldDeclarationComment) { let messageDescriptor = this.registry.resolveTypeName(field.T().typeName); runtime_1.assert(plugin_framework_1.DescriptorProto.is(messageDescriptor)); // writer.tag(<field no>, WireType.LengthDelimited).fork(); let writeTagAndFork = this.makeWriterCall(this.makeWriterTagCall(source, 'writer', field.no, rt.WireType.LengthDelimited), 'fork'); // MessageFieldMessage_TestMessage.internalBinaryWrite(message.messageField, <writeTagAndFork>, options); let binaryWrite = ts.createCall(ts.createPropertyAccess(ts.createIdentifier(this.imports.type(source, messageDescriptor)), ts.createIdentifier("internalBinaryWrite")), undefined, [fieldPropertyAccess, writeTagAndFork, ts.createIdentifier("options")]); // <...>.join() let binaryWriteAndJoin = this.makeWriterCall(binaryWrite, 'join'); // if (message.messageField) { let statement = ts.createIf(fieldPropertyAccess, ts.createExpressionStatement(binaryWriteAndJoin), undefined); ts.addSyntheticLeadingComment(statement, ts.SyntaxKind.MultiLineCommentTrivia, ' ' + fieldDeclarationComment + ' ', true); return [statement]; } messageRepeated(source, field, fieldPropertyAccess, fieldDeclarationComment) { let messageDescriptor = this.registry.resolveTypeName(field.T().typeName); runtime_1.assert(plugin_framework_1.DescriptorProto.is(messageDescriptor)); // message.repeatedMessageField[i] let fieldPropI = ts.createElementAccess(fieldPropertyAccess, ts.createIdentifier("i")); // writer.tag(<field no>, WireType.LengthDelimited).fork(); let writeTagAndFork = this.makeWriterCall(this.makeWriterTagCall(source, 'writer', field.no, rt.WireType.LengthDelimited), 'fork'); // MessageFieldMessage_TestMessage.internalBinaryWrite(message.repeatedMessageField, <writeTagAndFork>, options); let binaryWrite = ts.createCall(ts.createPropertyAccess(ts.createIdentifier(this.imports.type(source, messageDescriptor)), ts.createIdentifier("internalBinaryWrite")), undefined, [fieldPropI, writeTagAndFork, ts.createIdentifier("options")]); // <...>.join() let binaryWriteAndJoin = this.makeWriterCall(binaryWrite, 'join'); // for (let i = 0; i < message.repeatedMessageField.length; i++) { let statement = ts.createFor(ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createIdentifier("i"), undefined, ts.createNumericLiteral("0"))], ts.NodeFlags.Let), ts.createBinary(ts.createIdentifier("i"), ts.createToken(ts.SyntaxKind.LessThanToken), ts.createPropertyAccess(fieldPropertyAccess, ts.createIdentifier("length"))), ts.createPostfix(ts.createIdentifier("i"), ts.SyntaxKind.PlusPlusToken), ts.createExpressionStatement(binaryWriteAndJoin)); ts.addSyntheticLeadingComment(statement, ts.SyntaxKind.MultiLineCommentTrivia, ' ' + fieldDeclarationComment + ' ', true); return [statement]; } messageOneof(source, field, fieldDeclarationComment) { let messageDescriptor = this.registry.resolveTypeName(field.T().typeName); runtime_1.assert(plugin_framework_1.DescriptorProto.is(messageDescriptor)); // message.<oneof name> let groupPropertyAccess = ts.createPropertyAccess(ts.createIdentifier("message"), ts.createIdentifier(field.oneof)); // writer.tag(<field no>, WireType.LengthDelimited).fork(); let writeTagAndFork = this.makeWriterCall(this.makeWriterTagCall(source, 'writer', field.no, rt.WireType.LengthDelimited), 'fork'); // MessageFieldMessage_TestMessage.internalBinaryWrite(message.<groupPropertyAccess>.<fieldLocalName>, <writeTagAndFork>, options); let binaryWrite = ts.createCall(ts.createPropertyAccess(ts.createIdentifier(this.imports.type(source, messageDescriptor)), ts.createIdentifier("internalBinaryWrite")), undefined, [ ts.createPropertyAccess(groupPropertyAccess, field.localName), writeTagAndFork, ts.createIdentifier("options") ]); // <...>.join() let binaryWriteAndJoin = this.makeWriterCall(binaryWrite, 'join'); // if (message.objects.oneofKind === 'a') { let statement = ts.createIf(ts.createBinary(ts.createPropertyAccess(groupPropertyAccess, ts.createIdentifier(this.options.oneofKindDiscriminator)), ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.createStringLiteral(field.localName)), ts.createExpressionStatement(binaryWriteAndJoin), undefined); ts.addSyntheticLeadingComment(statement, ts.SyntaxKind.MultiLineCommentTrivia, ' ' + fieldDeclarationComment + ' ', true); return [statement]; } map(source, field, fieldPropertyAccess, fieldDeclarationComment) { // all javascript property keys are strings, need to do some conversion for wire format let mapEntryKeyRead; let mapEntryValueRead = ts.createElementAccess(fieldPropertyAccess, ts.createIdentifier("k")); switch (field.K) { case rt.ScalarType.BOOL: // parse bool for writer mapEntryKeyRead = ts.createBinary(ts.createIdentifier("k"), ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), ts.createStringLiteral("true")); break; case rt.ScalarType.INT32: case rt.ScalarType.SINT32: case rt.ScalarType.UINT32: case rt.ScalarType.FIXED32: case rt.ScalarType.SFIXED32: // parse int for writer mapEntryKeyRead = ts.createCall(ts.createIdentifier("parseInt"), undefined, [ts.createIdentifier("k")]); // convince compiler key works for index type // message.int32KeyedMap[k as any] // ^^^^^^^^^^ mapEntryValueRead = ts.createElementAccess(fieldPropertyAccess, ts.createAsExpression(ts.createIdentifier("k"), ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword))); break; default: // writer method accepts string for all other cases, no need to modify mapEntryKeyRead = ts.createIdentifier("k"); break; } // loop body for every map entry. looks different for messages. let forBody; if (field.V.kind == "message") { let messageDescriptor = this.registry.resolveTypeName(field.V.T().typeName); runtime_1.assert(plugin_framework_1.DescriptorProto.is(messageDescriptor)); forBody = ts.createBlock([ // same as for scalar maps ts.createExpressionStatement(this.makeWriterCall( // this.makeWriterCall( this.makeWriterTagCall(source, this.makeWriterCall(this.makeWriterTagCall(source, "writer", field.no, rt.WireType.LengthDelimited), // .fork // start length delimited for the MapEntry 'fork'), 1, this.wireTypeForSingleScalar(field.K)), // .string(message.strStrField[k]) // MapEntry key value field.K, mapEntryKeyRead)), // ts.createExpressionStatement(this.makeWriterCall(this.makeWriterTagCall(source, 'writer', 2, rt.WireType.LengthDelimited), // start length delimited for the message 'fork')), // MessageMapMessage_MyItem.internalBinaryWrite(message.strMsgField[k], writer, options); ts.createExpressionStatement(ts.createCall(ts.createPropertyAccess(ts.createIdentifier(this.imports.type(source, messageDescriptor)), ts.createIdentifier("internalBinaryWrite")), undefined, [ mapEntryValueRead, ts.createIdentifier("writer"), ts.createIdentifier("options") ])), // end message and end map entry ts.createExpressionStatement(this.makeWriterCall(this.makeWriterCall('writer', 'join'), 'join')), ], true); } else { // handle enum as INT32 let mapEntryValueScalarType = field.V.kind == "enum" ? rt.ScalarType.INT32 : field.V.T; // *rolleyes* forBody = ts.createExpressionStatement(this.makeWriterCall(this.makeWriterCall(this.makeWriterTagCall(source, this.makeWriterCall(this.makeWriterTagCall(source, this.makeWriterCall(this.makeWriterTagCall(source, 'writer', // tag for our field field.no, rt.WireType.LengthDelimited), // .fork // start length delimited for the MapEntry 'fork'), // MapEntry key field tag 1, this.wireTypeForSingleScalar(field.K)), // .string(message.strStrField[k]) // MapEntry key value field.K, mapEntryKeyRead), // MapEntry value field tag 2, this.wireTypeForSingleScalar(mapEntryValueScalarType)), // .string(message.strStrField[k]) // MapEntry value value mapEntryValueScalarType, mapEntryValueRead), 'join')); } // for (let k of Object.keys(message.strStrField)) let statement = ts.createForOf(undefined, ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createIdentifier("k"), undefined, undefined)], ts.NodeFlags.Let), ts.createCall(ts.createPropertyAccess(ts.createPropertyAccess(ts.createIdentifier("globalThis"), ts.createIdentifier("Object")), ts.createIdentifier("keys")), undefined, [fieldPropertyAccess]), forBody); ts.addSyntheticLeadingComment(statement, ts.SyntaxKind.MultiLineCommentTrivia, ' ' + fieldDeclarationComment + ' ', true); return [statement]; } makeWriterCall(writerExpressionOrName, type, argument) { let methodName = typeof type == "string" ? type : plugin_framework_1.StringFormat.formatScalarType(type); let writerExpression = typeof writerExpressionOrName == "string" ? ts.createIdentifier(writerExpressionOrName) : writerExpressionOrName; let methodProp = ts.createPropertyAccess(writerExpression, ts.createIdentifier(methodName)); return ts.createCall(methodProp, undefined, argument ? [argument] : undefined); } makeWriterTagCall(source, writerExpressionOrName, fieldNo, wireType) { let writerExpression = typeof writerExpressionOrName == "string" ? ts.createIdentifier(writerExpressionOrName) : writerExpressionOrName; let methodProp = ts.createPropertyAccess(writerExpression, ts.createIdentifier("tag")); let wireTypeName; switch (wireType) { case rt.WireType.LengthDelimited: wireTypeName = "LengthDelimited"; break; case rt.WireType.Bit64: wireTypeName = "Bit64"; break; case rt.WireType.Bit32: wireTypeName = "Bit32"; break; case rt.WireType.Varint: wireTypeName = "Varint"; break; case rt.WireType.EndGroup: wireTypeName = "EndGroup"; break; case rt.WireType.StartGroup: wireTypeName = "StartGroup"; break; } let wireTypeAccess = ts.createPropertyAccess(ts.createIdentifier(this.imports.name(source, 'WireType', this.options.runtimeImportPath)), wireTypeName); return ts.createCall(methodProp, undefined, [ ts.createNumericLiteral(fieldNo.toString()), wireTypeAccess ]); } wireTypeForSingleScalar(scalarType) { let wireType; switch (scalarType) { case rt.ScalarType.BOOL: case rt.ScalarType.INT32: case rt.ScalarType.UINT32: case rt.ScalarType.SINT32: case rt.ScalarType.INT64: case rt.ScalarType.UINT64: case rt.ScalarType.SINT64: wireType = rt.WireType.Varint; break; case rt.ScalarType.BYTES: case rt.ScalarType.STRING: wireType = rt.WireType.LengthDelimited; break; case rt.ScalarType.DOUBLE: case rt.ScalarType.FIXED64: case rt.ScalarType.SFIXED64: wireType = rt.WireType.Bit64; break; case rt.ScalarType.FLOAT: case rt.ScalarType.FIXED32: case rt.ScalarType.SFIXED32: wireType = rt.WireType.Bit32; break; } return wireType; } } exports.InternalBinaryWrite = InternalBinaryWrite;