From d4913bec9c1b9ce3ad052de6349eb89b2cd6677a Mon Sep 17 00:00:00 2001 From: Timo Stamm Date: Fri, 27 Oct 2023 15:52:18 +0200 Subject: [PATCH] Fix Node16 module resolution with @bufbuild/protoplugin (#598) --- packages/protobuf/src/codegen-info.ts | 39 ++- packages/protobuf/src/private/options-map.ts | 2 +- packages/protobuf/src/private/reify-wkt.ts | 2 + packages/protoplugin/src/ecmascript/index.ts | 4 +- .../protoplugin/src/ecmascript/reify-wkt.ts | 292 ++++++++++++++++++ 5 files changed, 329 insertions(+), 10 deletions(-) create mode 100644 packages/protoplugin/src/ecmascript/reify-wkt.ts diff --git a/packages/protobuf/src/codegen-info.ts b/packages/protobuf/src/codegen-info.ts index b86a7418f..f6c0d3756 100644 --- a/packages/protobuf/src/codegen-info.ts +++ b/packages/protobuf/src/codegen-info.ts @@ -20,17 +20,42 @@ import { import { getUnwrappedFieldType } from "./private/field-wrapper.js"; import { scalarDefaultValue } from "./private/scalars.js"; import { reifyWkt } from "./private/reify-wkt.js"; +import type { + DescEnum, + DescEnumValue, + DescField, + DescMessage, + DescMethod, + DescOneof, + DescService, +} from "./descriptor-set"; +import { LongType, ScalarType } from "./field.js"; interface CodegenInfo { + /** + * Name of the runtime library NPM package. + */ readonly packageName: string; - readonly localName: typeof localName; + readonly localName: ( + desc: + | DescEnum + | DescEnumValue + | DescMessage + | DescOneof + | DescField + | DescService + | DescMethod, + ) => string; readonly symbols: Record; - readonly getUnwrappedFieldType: typeof getUnwrappedFieldType; + readonly getUnwrappedFieldType: (field: DescField) => ScalarType | undefined; readonly wktSourceFiles: readonly string[]; - readonly scalarDefaultValue: typeof scalarDefaultValue; + readonly scalarDefaultValue: (type: ScalarType, longType: LongType) => any; // eslint-disable-line @typescript-eslint/no-explicit-any + /** + * @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead + */ readonly reifyWkt: typeof reifyWkt; - readonly safeIdentifier: typeof safeIdentifier; - readonly safeObjectProperty: typeof safeObjectProperty; + readonly safeIdentifier: (name: string) => string; + readonly safeObjectProperty: (name: string) => string; } type RuntimeSymbolName = @@ -64,7 +89,7 @@ type RuntimeSymbolInfo = { const packageName = "@bufbuild/protobuf"; export const codegenInfo: CodegenInfo = { - packageName, + packageName: "@bufbuild/protobuf", localName, reifyWkt, getUnwrappedFieldType, @@ -108,4 +133,4 @@ export const codegenInfo: CodegenInfo = { "google/protobuf/type.proto", "google/protobuf/wrappers.proto", ], -} as const; +}; diff --git a/packages/protobuf/src/private/options-map.ts b/packages/protobuf/src/private/options-map.ts index bdefdbea7..e80f8ea63 100644 --- a/packages/protobuf/src/private/options-map.ts +++ b/packages/protobuf/src/private/options-map.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { JsonValue } from "../json-format"; +import type { JsonValue } from "../json-format.js"; /** * diff --git a/packages/protobuf/src/private/reify-wkt.ts b/packages/protobuf/src/private/reify-wkt.ts index 9b3c8c1e7..07dfc8893 100644 --- a/packages/protobuf/src/private/reify-wkt.ts +++ b/packages/protobuf/src/private/reify-wkt.ts @@ -91,6 +91,8 @@ type DescWkt = }; /** + * @deprecated please use reifyWkt from @bufbuild/protoplugin/ecmascript instead + * * Reifies a given DescMessage into a more concrete object representing its * respective well-known type. The returned object will contain properties * representing the WKT's defined fields. diff --git a/packages/protoplugin/src/ecmascript/index.ts b/packages/protoplugin/src/ecmascript/index.ts index 30c6225d4..c16f2df6b 100644 --- a/packages/protoplugin/src/ecmascript/index.ts +++ b/packages/protoplugin/src/ecmascript/index.ts @@ -13,14 +13,14 @@ // limitations under the License. import { codegenInfo } from "@bufbuild/protobuf"; - +export { reifyWkt } from "./reify-wkt.js"; export { Target } from "./target.js"; export { Schema } from "./schema.js"; export { RuntimeImports } from "./runtime-imports.js"; export { GeneratedFile, FileInfo, Printable } from "./generated-file.js"; export { ImportSymbol } from "./import-symbol.js"; -export const { localName, reifyWkt } = codegenInfo; +export const { localName } = codegenInfo; export { createJsDocBlock, diff --git a/packages/protoplugin/src/ecmascript/reify-wkt.ts b/packages/protoplugin/src/ecmascript/reify-wkt.ts new file mode 100644 index 000000000..028a3e901 --- /dev/null +++ b/packages/protoplugin/src/ecmascript/reify-wkt.ts @@ -0,0 +1,292 @@ +// Copyright 2021-2023 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 type { DescField, DescMessage, DescOneof } from "@bufbuild/protobuf"; +import { ScalarType } from "@bufbuild/protobuf"; + +type DescWkt = + | { + typeName: "google.protobuf.Any"; + typeUrl: DescField; + value: DescField; + } + | { + typeName: "google.protobuf.Timestamp"; + seconds: DescField; + nanos: DescField; + } + | { + typeName: "google.protobuf.Duration"; + seconds: DescField; + nanos: DescField; + } + | { + typeName: "google.protobuf.Struct"; + fields: DescField & { fieldKind: "map" }; + } + | { + typeName: "google.protobuf.Value"; + kind: DescOneof; + nullValue: DescField & { fieldKind: "enum" }; + numberValue: DescField; + stringValue: DescField; + boolValue: DescField; + structValue: DescField & { fieldKind: "message" }; + listValue: DescField & { fieldKind: "message" }; + } + | { + typeName: "google.protobuf.ListValue"; + values: DescField & { fieldKind: "message" }; + } + | { + typeName: "google.protobuf.FieldMask"; + paths: DescField; + } + | { + typeName: "google.protobuf.DoubleValue"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.FloatValue"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.Int64Value"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.UInt64Value"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.Int32Value"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.UInt32Value"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.BoolValue"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.StringValue"; + value: DescField & { fieldKind: "scalar" }; + } + | { + typeName: "google.protobuf.BytesValue"; + value: DescField & { fieldKind: "scalar" }; + }; + +/** + * Reifies a given DescMessage into a more concrete object representing its + * respective well-known type. The returned object will contain properties + * representing the WKT's defined fields. + * + * Useful during code generation when immediate access to a particular field + * is needed without having to search the object's typename and DescField list. + * + * Returns undefined if the WKT cannot be completely constructed via the + * DescMessage. + */ +export function reifyWkt(message: DescMessage): DescWkt | undefined { + switch (message.typeName) { + case "google.protobuf.Any": { + const typeUrl = message.fields.find( + (f) => + f.number == 1 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.STRING, + ); + const value = message.fields.find( + (f) => + f.number == 2 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.BYTES, + ); + if (typeUrl && value) { + return { + typeName: message.typeName, + typeUrl, + value, + }; + } + break; + } + case "google.protobuf.Timestamp": { + const seconds = message.fields.find( + (f) => + f.number == 1 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT64, + ); + const nanos = message.fields.find( + (f) => + f.number == 2 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT32, + ); + if (seconds && nanos) { + return { + typeName: message.typeName, + seconds, + nanos, + }; + } + break; + } + case "google.protobuf.Duration": { + const seconds = message.fields.find( + (f) => + f.number == 1 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT64, + ); + const nanos = message.fields.find( + (f) => + f.number == 2 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.INT32, + ); + if (seconds && nanos) { + return { + typeName: message.typeName, + seconds, + nanos, + }; + } + break; + } + case "google.protobuf.Struct": { + const fields = message.fields.find((f) => f.number == 1 && !f.repeated); + if ( + fields?.fieldKind !== "map" || + fields.mapValue.kind !== "message" || + fields.mapValue.message.typeName !== "google.protobuf.Value" + ) { + break; + } + return { typeName: message.typeName, fields }; + } + case "google.protobuf.Value": { + const kind = message.oneofs.find((o) => o.name === "kind"); + const nullValue = message.fields.find( + (f) => f.number == 1 && f.oneof === kind, + ); + if ( + nullValue?.fieldKind !== "enum" || + nullValue.enum.typeName !== "google.protobuf.NullValue" + ) { + return undefined; + } + const numberValue = message.fields.find( + (f) => + f.number == 2 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.DOUBLE && + f.oneof === kind, + ); + const stringValue = message.fields.find( + (f) => + f.number == 3 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.STRING && + f.oneof === kind, + ); + const boolValue = message.fields.find( + (f) => + f.number == 4 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.BOOL && + f.oneof === kind, + ); + const structValue = message.fields.find( + (f) => f.number == 5 && f.oneof === kind, + ); + if ( + structValue?.fieldKind !== "message" || + structValue.message.typeName !== "google.protobuf.Struct" + ) { + return undefined; + } + const listValue = message.fields.find( + (f) => f.number == 6 && f.oneof === kind, + ); + if ( + listValue?.fieldKind !== "message" || + listValue.message.typeName !== "google.protobuf.ListValue" + ) { + return undefined; + } + if (kind && numberValue && stringValue && boolValue) { + return { + typeName: message.typeName, + kind, + nullValue, + numberValue, + stringValue, + boolValue, + structValue, + listValue, + }; + } + break; + } + case "google.protobuf.ListValue": { + const values = message.fields.find((f) => f.number == 1 && f.repeated); + if ( + values?.fieldKind != "message" || + values.message.typeName !== "google.protobuf.Value" + ) { + break; + } + return { typeName: message.typeName, values }; + } + case "google.protobuf.FieldMask": { + const paths = message.fields.find( + (f) => + f.number == 1 && + f.fieldKind == "scalar" && + f.scalar === ScalarType.STRING && + f.repeated, + ); + if (paths) { + return { typeName: message.typeName, paths }; + } + break; + } + case "google.protobuf.DoubleValue": + case "google.protobuf.FloatValue": + case "google.protobuf.Int64Value": + case "google.protobuf.UInt64Value": + case "google.protobuf.Int32Value": + case "google.protobuf.UInt32Value": + case "google.protobuf.BoolValue": + case "google.protobuf.StringValue": + case "google.protobuf.BytesValue": { + const value = message.fields.find( + (f) => f.number == 1 && f.name == "value", + ); + if (!value) { + break; + } + if (value.fieldKind !== "scalar") { + break; + } + return { typeName: message.typeName, value }; + } + } + return undefined; +}