UNPKG

@bufbuild/protoplugin

Version:

Helps to create your own Protocol Buffers code generators.

286 lines (285 loc) 10.5 kB
"use strict"; // Copyright 2021-2025 Buf Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. Object.defineProperty(exports, "__esModule", { value: true }); exports.getPackageComments = getPackageComments; exports.getSyntaxComments = getSyntaxComments; exports.getComments = getComments; exports.getFeatureOptionStrings = getFeatureOptionStrings; exports.getDeclarationString = getDeclarationString; const protobuf_1 = require("@bufbuild/protobuf"); const reflect_1 = require("@bufbuild/protobuf/reflect"); const wkt_1 = require("@bufbuild/protobuf/wkt"); /** * Get comments on the package element in the protobuf source. */ function getPackageComments(desc) { return findComments(desc.proto.sourceCodeInfo, [ wkt_1.FileDescriptorProtoSchema.field.package.number, ]); } /** * Get comments on the syntax element in the protobuf source. */ function getSyntaxComments(desc) { return findComments(desc.proto.sourceCodeInfo, [ wkt_1.FileDescriptorProtoSchema.field.syntax.number, ]); } /** * Get comments on the element in the protobuf source. */ function getComments(desc) { let path = []; let file; switch (desc.kind) { case "enum": path = desc.parent ? [ ...getComments(desc.parent).sourcePath, wkt_1.DescriptorProtoSchema.field.enumType.number, desc.parent.proto.enumType.indexOf(desc.proto), ] : [ wkt_1.FileDescriptorProtoSchema.field.enumType.number, desc.file.proto.enumType.indexOf(desc.proto), ]; file = desc.file; break; case "oneof": path = [ ...getComments(desc.parent).sourcePath, wkt_1.DescriptorProtoSchema.field.oneofDecl.number, desc.parent.proto.oneofDecl.indexOf(desc.proto), ]; file = desc.parent.file; break; case "message": path = desc.parent ? [ ...getComments(desc.parent).sourcePath, wkt_1.DescriptorProtoSchema.field.nestedType.number, desc.parent.proto.nestedType.indexOf(desc.proto), ] : [ wkt_1.FileDescriptorProtoSchema.field.messageType.number, desc.file.proto.messageType.indexOf(desc.proto), ]; file = desc.file; break; case "enum_value": path = [ ...getComments(desc.parent).sourcePath, wkt_1.EnumDescriptorProtoSchema.field.value.number, desc.parent.proto.value.indexOf(desc.proto), ]; file = desc.parent.file; break; case "field": path = [ ...getComments(desc.parent).sourcePath, wkt_1.DescriptorProtoSchema.field.field.number, desc.parent.proto.field.indexOf(desc.proto), ]; file = desc.parent.file; break; case "extension": path = desc.parent ? [ ...getComments(desc.parent).sourcePath, wkt_1.DescriptorProtoSchema.field.extension.number, desc.parent.proto.extension.indexOf(desc.proto), ] : [ wkt_1.FileDescriptorProtoSchema.field.extension.number, desc.file.proto.extension.indexOf(desc.proto), ]; file = desc.file; break; case "service": path = [ wkt_1.FileDescriptorProtoSchema.field.service.number, desc.file.proto.service.indexOf(desc.proto), ]; file = desc.file; break; case "rpc": path = [ ...getComments(desc.parent).sourcePath, wkt_1.ServiceDescriptorProtoSchema.field.method.number, desc.parent.proto.method.indexOf(desc.proto), ]; file = desc.parent.file; break; } return findComments(file.proto.sourceCodeInfo, path); } /** * Get feature options set on the element in the protobuf source. This returns * compact (e.g. fields) or regular options (e.g. files) as an array of strings. */ function getFeatureOptionStrings(desc) { var _a, _b; const strings = []; const features = (_a = desc.proto.options) === null || _a === void 0 ? void 0 : _a.features; if (features !== undefined) { const r = (0, reflect_1.reflect)(wkt_1.FeatureSetSchema, features); for (const f of r.fields) { if (f.fieldKind != "enum" || !r.isSet(f)) { continue; } const val = r.get(f); const name = (_b = f.enum.values.find((v) => v.number == val)) === null || _b === void 0 ? void 0 : _b.name; if (name !== undefined) { strings.push(`features.${f.name} = ${name}`); } } } return strings; } /** * Return a string that matches the definition of a field in the protobuf * source. Does not take custom options into account. */ function getDeclarationString(desc) { var _a; if (desc.kind === "enum_value") { let str = `${desc.name} = ${desc.number}`; if (((_a = desc.proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) === true) { str += " [deprecated = true]"; } return str; } const parts = []; function typeName(f) { if (f.message) { return f.message.typeName; } if (f.enum) { return f.enum.typeName; } return protobuf_1.ScalarType[f.scalar].toLowerCase(); } switch (desc.fieldKind) { case "scalar": case "enum": case "message": if (fieldHasRequiredKeyword(desc)) { parts.push("required"); } if (fieldHasOptionalKeyword(desc)) { parts.push("optional"); } parts.push(typeName(desc)); break; case "list": parts.push("repeated", typeName(desc)); break; case "map": { const k = protobuf_1.ScalarType[desc.mapKey].toLowerCase(); const v = typeName(desc); parts.push(`map<${k}, ${v}>`); break; } } parts.push(desc.name, "=", desc.number.toString()); const options = []; const protoOptions = desc.proto.options; if (protoOptions !== undefined && (0, protobuf_1.isFieldSet)(protoOptions, wkt_1.FieldOptionsSchema.field.packed)) { options.push(`packed = ${protoOptions.packed.toString()}`); } if ((0, protobuf_1.isFieldSet)(desc.proto, wkt_1.FieldDescriptorProtoSchema.field.defaultValue)) { let defaultValue = desc.proto.defaultValue; if (desc.proto.type == wkt_1.FieldDescriptorProto_Type.BYTES || desc.proto.type == wkt_1.FieldDescriptorProto_Type.STRING) { defaultValue = '"' + defaultValue.replace('"', '\\"') + '"'; } options.push(`default = ${defaultValue}`); } if (desc.kind == "field" && desc.jsonName !== (0, reflect_1.protoCamelCase)(desc.name)) { options.push(`json_name = "${desc.jsonName}"`); } if (protoOptions !== undefined && (0, protobuf_1.isFieldSet)(protoOptions, wkt_1.FieldOptionsSchema.field.jstype)) { options.push(`jstype = ${wkt_1.FieldOptions_JSType[protoOptions.jstype]}`); } if (protoOptions !== undefined && (0, protobuf_1.isFieldSet)(protoOptions, wkt_1.FieldOptionsSchema.field.deprecated)) { options.push("deprecated = true"); } options.push(...getFeatureOptionStrings(desc)); if (options.length > 0) { parts.push("[" + options.join(", ") + "]"); } return parts.join(" "); } /** * Whether this field was declared with `required` in the protobuf source. */ function fieldHasRequiredKeyword(field) { const edition = (field.kind == "extension" ? field.file : field.parent.file) .edition; return (edition == wkt_1.Edition.EDITION_PROTO2 && field.proto.label == wkt_1.FieldDescriptorProto_Label.REQUIRED); } /** * Whether this field was declared with `optional` in the protobuf source. * Note that message fields are always optional. It is impossible to determine * whether the keyword was used. */ function fieldHasOptionalKeyword(field) { const edition = (field.kind == "extension" ? field.file : field.parent.file) .edition; if (edition == wkt_1.Edition.EDITION_PROTO2) { return (!field.oneof && field.proto.label == wkt_1.FieldDescriptorProto_Label.OPTIONAL); } if (edition == wkt_1.Edition.EDITION_PROTO3) { return field.proto.proto3Optional; } return false; } /** * Find comments. */ function findComments(sourceCodeInfo, sourcePath) { if (!sourceCodeInfo) { return { leadingDetached: [], sourcePath, }; } for (const location of sourceCodeInfo.location) { if (location.path.length !== sourcePath.length) { continue; } if (location.path.some((value, index) => sourcePath[index] !== value)) { continue; } return { leadingDetached: location.leadingDetachedComments, leading: (0, protobuf_1.isFieldSet)(location, wkt_1.SourceCodeInfo_LocationSchema.field.leadingComments) ? location.leadingComments : undefined, trailing: (0, protobuf_1.isFieldSet)(location, wkt_1.SourceCodeInfo_LocationSchema.field.trailingComments) ? location.trailingComments : undefined, sourcePath, }; } return { leadingDetached: [], sourcePath, }; }