UNPKG

@bufbuild/protoplugin

Version:

Helps to create your own Protocol Buffers code generators.

221 lines (220 loc) 7.55 kB
// 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. import { wktPublicImportPaths } from "@bufbuild/protobuf/codegenv2"; import { nestedTypes } from "@bufbuild/protobuf/reflect"; import { safeIdentifier } from "./safe-identifier.js"; /** * Return a file path for the give file descriptor. */ export function generateFilePath(file, bootstrapWkt, filesToGenerate) { // Well-known types are published with the runtime package. We usually want // the generated code to import them from the runtime package, with the // following exceptions: // 1. We are bootstrapping the runtime package via the plugin option // "bootstrap_wkt". In that case, we cannot refer to the runtime package // itself. // 2. We were explicitly asked to generate the well-known type. const wktFrom = wktPublicImportPaths[file.proto.name]; if (wktFrom !== undefined && !bootstrapWkt && !filesToGenerate.find((f) => f.name === file.name)) { return wktFrom; } return "./" + file.name + "_pb.js"; } /** * Return a safe identifier for a generated descriptor. */ export function generatedDescName(desc) { const file = desc.kind == "file" ? desc : desc.file; const { descNames } = allNames(file); const name = descNames.get(desc); if (name === undefined) { throw new Error(`unable to determine unique descriptor name for ${desc.toString()}`); } return name; } /** * Return a safe identifier for a generated shape. */ export function generatedShapeName(desc) { const { shapeNames } = allNames(desc.file); const name = shapeNames.get(desc); if (name === undefined) { throw new Error(`unable to determine unique shape name for ${desc.toString()}`); } return name; } /** * Return a safe identifier for a generated JSON type. */ export function generatedJsonTypeName(desc) { const { jsonTypeNames } = allNames(desc.file); const name = jsonTypeNames.get(desc); if (name === undefined) { throw new Error(`unable to determine unique json type name for ${desc.toString()}`); } return name; } /** * Return a safe identifier for a generated Valid type. */ export function generatedValidTypeName(desc) { const { validTypeNames } = allNames(desc.file); const name = validTypeNames.get(desc); if (name === undefined) { throw new Error(`unable to determine unique valid type name for ${desc.toString()}`); } return name; } /** * Compute the ideal name for a generated descriptor. */ function idealDescName(desc, i) { const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`; if (desc.kind == "file") { const name = "file_" + desc.name.replace(/[^a-zA-Z0-9_]+/g, "_"); return safeIdentifier(name + salt); } switch (desc.kind) { case "enum": return safeIdentifier(identifier(desc) + "Schema" + salt); case "message": return safeIdentifier(identifier(desc) + "Schema" + salt); case "extension": return safeIdentifier(identifier(desc) + salt); case "service": return safeIdentifier(identifier(desc) + salt); } } /** * Compute the ideal name for a generated shape. */ function idealShapeName(desc, i) { const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`; return safeIdentifier(identifier(desc) + salt); } /** * Compute the ideal name for a generated JSON type. */ function idealJsonTypeName(desc, i) { const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`; return safeIdentifier(identifier(desc) + "Json" + salt); } /** * Compute the ideal name for a generated Valid type. */ function idealValidTypeName(desc, i) { const salt = i === 0 ? "" : i === 1 ? "$" : `$${i - 1}`; return safeIdentifier(identifier(desc) + "Valid" + salt); } /** * Return an identifier for the given descriptor based on its type name. * * The type name for a protobuf message is the package name (if any), plus * the names of parent messages it is nested in (if any), plus the name of * the element, separated by dots. For example: foo.bar.ParentMsg.MyEnum. * * ECMAScript does not have packages or namespaces, so we need a single * identifier. Our convention is to drop the package name, and to join other * parts of the name with an underscore. For example: ParentMsg_MyEnum. */ function identifier(desc) { const pkg = desc.file.proto.package; const offset = pkg.length > 0 ? pkg.length + 1 : 0; const nameWithoutPkg = desc.typeName.substring(offset); return nameWithoutPkg.replace(/\./g, "_"); } /** * Compute all ideal names for the elements in the file, resolving name clashes. */ function allNames(file) { const taken = new Set(); // In the first pass, register shape names const shapeNames = new Map(); for (const desc of nestedTypes(file)) { if (desc.kind != "enum" && desc.kind != "message") { continue; } let name; for (let i = 0;; i++) { name = idealShapeName(desc, i); if (!taken.has(name)) { break; } } taken.add(name); shapeNames.set(desc, name); } // In the second pass, register desc names const descNames = new Map(); for (const desc of [file, ...nestedTypes(file)]) { let name; switch (desc.kind) { case "enum": case "message": { for (let i = 0;; i++) { name = idealDescName(desc, i); if (!taken.has(name)) { break; } } break; } default: { for (let i = 0;; i++) { name = idealDescName(desc, i); if (!taken.has(name)) { break; } } break; } } taken.add(name); descNames.set(desc, name); } // In the third pass, register json type names const jsonTypeNames = new Map(); for (const desc of nestedTypes(file)) { if (desc.kind != "enum" && desc.kind != "message") { continue; } let name; for (let i = 0;; i++) { name = idealJsonTypeName(desc, i); if (!taken.has(name)) { break; } } taken.add(name); jsonTypeNames.set(desc, name); } const validTypeNames = new Map(); for (const desc of nestedTypes(file)) { if (desc.kind != "enum" && desc.kind != "message") { continue; } let name; for (let i = 0;; i++) { name = idealValidTypeName(desc, i); if (!taken.has(name)) { break; } } taken.add(name); validTypeNames.set(desc, name); } return { shapeNames, jsonTypeNames, validTypeNames, descNames }; }