diff --git a/packages/protobuf-bench/README.md b/packages/protobuf-bench/README.md index c070ad41e..b447f853e 100644 --- a/packages/protobuf-bench/README.md +++ b/packages/protobuf-bench/README.md @@ -10,5 +10,5 @@ server would usually do. | code generator | bundle size | minified | compressed | |---------------------|------------------------:|-----------------------:|-------------------:| -| protobuf-es | 126,685 b | 66,254 b | 15,996 b | +| protobuf-es | 126,744 b | 66,286 b | 16,005 b | | protobuf-javascript | 394,384 b | 288,654 b | 45,122 b | diff --git a/packages/protobuf-test/src/codegenv1/boot.test.ts b/packages/protobuf-test/src/codegenv1/boot.test.ts index aba9ce6d8..b4bbdaa6c 100644 --- a/packages/protobuf-test/src/codegenv1/boot.test.ts +++ b/packages/protobuf-test/src/codegenv1/boot.test.ts @@ -18,24 +18,22 @@ import { UpstreamProtobuf } from "upstream-protobuf"; import { join as joinPath } from "node:path"; import { readFileSync } from "fs"; import { clearField, equals, fromBinary, toBinary } from "@bufbuild/protobuf"; -import type { - DescriptorProto, - FileDescriptorProto, -} from "@bufbuild/protobuf/wkt"; import { + type DescriptorProto, + type FileDescriptorProto, DescriptorProtoDesc, - FieldDescriptorProtoDesc, FileDescriptorProtoDesc, FileDescriptorSetDesc, + FieldDescriptorProtoDesc, FieldOptionsDesc, } from "@bufbuild/protobuf/wkt"; import assert from "node:assert"; import { + boot, bootFileDescriptorProto, createFileDescriptorProtoBoot, embedFileDesc, } from "@bufbuild/protobuf/codegenv1"; -import { boot } from "@bufbuild/protobuf/codegenv1"; describe("boot()", () => { test("hydrates google/protobuf/descriptor.proto", async () => { @@ -95,12 +93,12 @@ describe("bootFileDescriptorProto()", () => { d.messageType.forEach(stripLikeBoot); return; } - clearField(DescriptorProtoDesc, d, "reservedRange"); - clearField(DescriptorProtoDesc, d, "reservedName"); + clearField(d, DescriptorProtoDesc.field.reservedRange); + clearField(d, DescriptorProtoDesc.field.reservedName); for (const f of d.field) { - clearField(FieldDescriptorProtoDesc, f, "jsonName"); + clearField(f, FieldDescriptorProtoDesc.field.jsonName); if (f.options) { - clearField(FieldOptionsDesc, f.options, "featureSupport"); + clearField(f.options, FieldOptionsDesc.field.featureSupport); } } for (const n of d.nestedType) { diff --git a/packages/protobuf-test/src/create.test.ts b/packages/protobuf-test/src/create.test.ts index f93f6a13e..075182f89 100644 --- a/packages/protobuf-test/src/create.test.ts +++ b/packages/protobuf-test/src/create.test.ts @@ -540,7 +540,7 @@ describe("create()", () => { expect(created.either).toStrictEqual(filled.either); break; default: - expect(isFieldSet(desc, created, name)).toBe(true); + expect(isFieldSet(created, desc.field[name])).toBe(true); expect(created[name]).toStrictEqual(filled[name]); } }); @@ -561,7 +561,7 @@ describe("create()", () => { expect(created.either).toStrictEqual(filled.either); break; default: - expect(isFieldSet(desc, created, name)).toBe(true); + expect(isFieldSet(created, desc.field[name])).toBe(true); expect(created[name]).toStrictEqual(filled[name]); } }); @@ -582,7 +582,7 @@ describe("create()", () => { expect(created.either).toStrictEqual(filled.either); break; default: - expect(isFieldSet(desc, created, name)).toBe(true); + expect(isFieldSet(created, desc.field[name])).toBe(true); expect(created[name]).toStrictEqual(filled[name]); } }); diff --git a/packages/protobuf-test/src/fields.test.ts b/packages/protobuf-test/src/fields.test.ts index f8a9c9642..bdabc7321 100644 --- a/packages/protobuf-test/src/fields.test.ts +++ b/packages/protobuf-test/src/fields.test.ts @@ -13,7 +13,6 @@ // limitations under the License. import { beforeEach, describe, expect, test } from "@jest/globals"; -import type { DescMessage } from "@bufbuild/protobuf"; import { clearField, create, isFieldSet } from "@bufbuild/protobuf"; import * as proto3_ts from "./gen/ts/extra/proto3_pb.js"; import * as proto2_ts from "./gen/ts/extra/proto2_pb.js"; @@ -26,41 +25,43 @@ import { } from "./helpers-edition2023.js"; describe("isFieldSet()", () => { - test("accepts field names", () => { + test("returns true for set field", () => { const msg = create(proto3_ts.Proto3MessageDesc); - isFieldSet(proto3_ts.Proto3MessageDesc, msg, "singularStringField"); - isFieldSet(proto3_ts.Proto3MessageDesc, msg, "optionalStringField"); - isFieldSet(proto3_ts.Proto3MessageDesc, msg, "repeatedStringField"); - isFieldSet(proto3_ts.Proto3MessageDesc, msg, "mapStringStringField"); - isFieldSet(proto3_ts.Proto3MessageDesc, msg, "oneofBoolField"); + msg.optionalStringField = "abc"; + const set = isFieldSet( + msg, + proto3_ts.Proto3MessageDesc.field.optionalStringField, + ); + expect(set).toBe(true); }); - test("rejects unknown field names", () => { + test("returns true for unset field", () => { const msg = create(proto3_ts.Proto3MessageDesc); - // @ts-expect-error TS2345 - isFieldSet(proto3_ts.Proto3MessageDesc, msg, "not a field name"); + const set = isFieldSet( + msg, + proto3_ts.Proto3MessageDesc.field.optionalStringField, + ); + expect(set).toBe(false); }); - test("rejects oneof names", () => { + test("returns false for foreign field", () => { const msg = create(proto3_ts.Proto3MessageDesc); - // @ts-expect-error TS2345 - isFieldSet(proto3_ts.Proto3MessageDesc, msg, "either"); - }); - test("accepts string for anonymous message", () => { - const desc: DescMessage = proto3_ts.Proto3MessageDesc; - const msg = create(desc); - const set = isFieldSet(desc, msg, "not a field name"); + msg.optionalStringField = "abc"; + const set = isFieldSet( + msg, + proto2_ts.Proto2MessageDesc.field.optionalStringField, + ); expect(set).toBe(false); }); describe("with proto3", () => { const desc = proto3_ts.Proto3MessageDesc; test.each(desc.fields)("%s is initially unset", (field) => { const msg = create(desc); - const set = isFieldSet(desc as DescMessage, msg, field.localName); + const set = isFieldSet(msg, field); expect(set).toBe(false); }); test.each(fillProto3MessageNames())("%s is set", (name) => { const msg = create(desc); fillProto3Message(msg); - const set = isFieldSet(desc, msg, name); + const set = isFieldSet(msg, desc.field[name]); expect(set).toBe(true); }); }); @@ -68,13 +69,13 @@ describe("isFieldSet()", () => { const desc = proto2_ts.Proto2MessageDesc; test.each(desc.fields)("%s is initially unset", (field) => { const msg = create(desc); - const set = isFieldSet(desc as DescMessage, msg, field.localName); + const set = isFieldSet(msg, field); expect(set).toBe(false); }); test.each(fillProto2MessageNames())("%s is set", (name) => { const msg = create(desc); fillProto2Message(msg); - const set = isFieldSet(desc, msg, name); + const set = isFieldSet(msg, desc.field[name]); expect(set).toBe(true); }); }); @@ -82,13 +83,13 @@ describe("isFieldSet()", () => { const desc = edition2023_ts.Edition2023MessageDesc; test.each(desc.fields)("%s is initially unset", (field) => { const msg = create(desc); - const set = isFieldSet(desc as DescMessage, msg, field.localName); + const set = isFieldSet(msg, field); expect(set).toBe(false); }); test.each(fillEdition2023MessageNames())("%s is set", (name) => { const msg = create(desc); fillEdition2023Message(msg); - const set = isFieldSet(desc, msg, name); + const set = isFieldSet(msg, desc.field[name]); expect(set).toBe(true); }); }); @@ -105,9 +106,9 @@ describe("clearField()", () => { fillProto3Message(msg); }); test.each(fillProto3MessageNames())("%s", (name) => { - expect(isFieldSet(desc, msg, name)).toBe(true); - clearField(desc, msg, name); - expect(isFieldSet(desc, msg, name)).toBe(false); + expect(isFieldSet(msg, desc.field[name])).toBe(true); + clearField(msg, desc.field[name]); + expect(isFieldSet(msg, desc.field[name])).toBe(false); switch (name) { case "oneofBoolField": expect(msg.either).toStrictEqual(zero.either); @@ -135,9 +136,9 @@ describe("clearField()", () => { fillProto2Message(msg); }); test.each(fillProto2MessageNames())("%s", (name) => { - expect(isFieldSet(desc, msg, name)).toBe(true); - clearField(desc, msg, name); - expect(isFieldSet(desc, msg, name)).toBe(false); + expect(isFieldSet(msg, desc.field[name])).toBe(true); + clearField(msg, desc.field[name]); + expect(isFieldSet(msg, desc.field[name])).toBe(false); switch (name) { case "oneofBoolField": expect(msg.either).toStrictEqual(zero.either); @@ -162,9 +163,9 @@ describe("clearField()", () => { fillEdition2023Message(msg); }); test.each(fillEdition2023MessageNames())("%s", (name) => { - expect(isFieldSet(desc, msg, name)).toBe(true); - clearField(desc, msg, name); - expect(isFieldSet(desc, msg, name)).toBe(false); + expect(isFieldSet(msg, desc.field[name])).toBe(true); + clearField(msg, desc.field[name]); + expect(isFieldSet(msg, desc.field[name])).toBe(false); switch (name) { case "oneofBoolField": expect(msg.either).toStrictEqual(zero.either); diff --git a/packages/protobuf-test/src/generate-code.test.ts b/packages/protobuf-test/src/generate-code.test.ts index 8c12acf2b..4c7c84601 100644 --- a/packages/protobuf-test/src/generate-code.test.ts +++ b/packages/protobuf-test/src/generate-code.test.ts @@ -225,8 +225,13 @@ describe("ts generated code is equal to js generated code", () => { if (id !== undefined) { return id; } - if ("toString" in value) { - id = String(value) + "@" + seen.size; + if ( + "toString" in value && + typeof value.toString == "function" && + Object.prototype.hasOwnProperty.call(value, "toString") + ) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- we're not calling Object.toString + id = value.toString() + "@" + seen.size; } else { id = "unknown@" + seen.size; } @@ -237,3 +242,11 @@ describe("ts generated code is equal to js generated code", () => { ); } }); + +describe("GenDescMessage.field", () => { + test("is type safe", () => { + proto3_ts.Proto3MessageDesc.field.optionalStringField; + // @ts-expect-error TS2339: Property foo does not exist on type + proto3_ts.Proto3MessageDesc.field.foo; + }); +}); diff --git a/packages/protobuf-test/src/helpers.ts b/packages/protobuf-test/src/helpers.ts index dbfa64156..b0580e668 100644 --- a/packages/protobuf-test/src/helpers.ts +++ b/packages/protobuf-test/src/helpers.ts @@ -12,11 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { DescMessage } from "@bufbuild/protobuf"; import { UpstreamProtobuf } from "upstream-protobuf"; import { createFileRegistry } from "@bufbuild/protobuf/reflect"; -import * as proto3_ts from "./gen/ts/extra/proto3_pb.js"; -import type { DescField } from "@bufbuild/protobuf"; import { fromBinary } from "@bufbuild/protobuf"; import type { FileDescriptorSet } from "@bufbuild/protobuf/wkt"; import { FileDescriptorSetDesc } from "@bufbuild/protobuf/wkt"; @@ -95,40 +92,3 @@ export async function compileMethod(proto: string) { assert(firstMethod); return firstMethod; } - -export function getFieldByLocalName(desc: DescMessage, name: string): DescField; -export function getFieldByLocalName( - desc: DescMessage, - name: string, - fieldKind: "message", -): DescField & { fieldKind: "message" }; -export function getFieldByLocalName( - desc: DescMessage, - name: string, - fieldKind: "list", -): DescField & { fieldKind: "list" }; -export function getFieldByLocalName( - desc: DescMessage, - name: string, - fieldKind: "map", -): DescField & { fieldKind: "map" }; -export function getFieldByLocalName( - desc: DescMessage, - name: string, - fieldKind?: string, -): DescField { - const field = proto3_ts.Proto3MessageDesc.fields.find( - (f) => f.localName === name, - ); - if (!field) { - throw new Error(`getFieldByLocalName: ${name} not found`); - } - if (fieldKind !== undefined) { - if (field.fieldKind != fieldKind) { - throw new Error( - `getFieldByLocalName: ${name} is not a ${fieldKind} field`, - ); - } - } - return field; -} diff --git a/packages/protobuf-test/src/reflect/reflect-list.test.ts b/packages/protobuf-test/src/reflect/reflect-list.test.ts index 3c533ee60..65ab2cf52 100644 --- a/packages/protobuf-test/src/reflect/reflect-list.test.ts +++ b/packages/protobuf-test/src/reflect/reflect-list.test.ts @@ -19,54 +19,38 @@ import { reflect, isReflectMessage, } from "@bufbuild/protobuf/reflect"; -import { getFieldByLocalName } from "../helpers.js"; import * as proto3_ts from "../gen/ts/extra/proto3_pb.js"; import { create, protoInt64 } from "@bufbuild/protobuf"; import { UserDesc } from "../gen/ts/extra/example_pb.js"; +import assert from "node:assert"; describe("reflectList()", () => { test("creates ReflectList", () => { - const f = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "repeatedStringField", - "list", - ); + const f = proto3_ts.Proto3MessageDesc.field.repeatedStringField; + assert(f.fieldKind == "list"); const list = reflectList(f); expect(typeof list.field).toBe("function"); expect(isReflectList(list)).toBe(true); }); test("creates ReflectList with unsafe input", () => { - const f = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "repeatedStringField", - "list", - ); + const f = proto3_ts.Proto3MessageDesc.field.repeatedStringField; + assert(f.fieldKind == "list"); const list = reflectList(f, [1, 2, 3]); expect(isReflectList(list)).toBe(true); }); }); describe("ReflectList", () => { - const repeatedStringField = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "repeatedStringField", - "list", - ); - const repeatedInt64Field = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "repeatedInt64Field", - "list", - ); - const repeatedInt64JsStringField = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "repeatedInt64JsStringField", - "list", - ); - const repeatedMessageField = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "repeatedMessageField", - "list", - ); + const { + repeatedStringField, + repeatedInt64Field, + repeatedInt64JsStringField, + repeatedMessageField, + } = proto3_ts.Proto3MessageDesc.field; + assert(repeatedStringField.fieldKind == "list"); + assert(repeatedInt64Field.fieldKind == "list"); + assert(repeatedInt64JsStringField.fieldKind == "list"); + assert(repeatedMessageField.fieldKind == "list"); const n0 = protoInt64.zero; const n1 = protoInt64.parse(1); const n2 = protoInt64.parse(2); diff --git a/packages/protobuf-test/src/reflect/reflect-map.test.ts b/packages/protobuf-test/src/reflect/reflect-map.test.ts index 565968f23..91e69128a 100644 --- a/packages/protobuf-test/src/reflect/reflect-map.test.ts +++ b/packages/protobuf-test/src/reflect/reflect-map.test.ts @@ -13,7 +13,6 @@ // limitations under the License. import { describe, expect, test } from "@jest/globals"; -import { getFieldByLocalName } from "../helpers.js"; import * as proto3_ts from "../gen/ts/extra/proto3_pb.js"; import { isReflectMap, @@ -24,51 +23,36 @@ import { import { protoInt64 } from "@bufbuild/protobuf"; import { UserDesc } from "../gen/ts/extra/example_pb.js"; import { create } from "@bufbuild/protobuf"; +import assert from "node:assert"; describe("reflectMap()", () => { test("creates ReflectMap", () => { - const mapStringStringField = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "mapStringStringField", - "map", - ); - const map = reflectMap(mapStringStringField); + const f = proto3_ts.Proto3MessageDesc.field.mapStringStringField; + assert(f.fieldKind == "map"); + const map = reflectMap(f); expect(typeof map.field).toBe("function"); expect(isReflectMap(map)).toBe(true); }); test("creates ReflectMap with unsafe input", () => { - const mapStringStringField = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "mapStringStringField", - "map", - ); - const map = reflectMap(mapStringStringField, { x: 123 }); + const f = proto3_ts.Proto3MessageDesc.field.mapStringStringField; + assert(f.fieldKind == "map"); + const map = reflectMap(f, { x: 123 }); expect(typeof map.field).toBe("function"); expect(isReflectMap(map)).toBe(true); }); }); describe("ReflectMap", () => { - const mapStringStringField = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "mapStringStringField", - "map", - ); - const mapInt64Int64Field = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "mapInt64Int64Field", - "map", - ); - const mapInt32Int32Field = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "mapInt32Int32Field", - "map", - ); - const mapInt32MessageField = getFieldByLocalName( - proto3_ts.Proto3MessageDesc, - "mapInt32MessageField", - "map", - ); + const { + mapStringStringField, + mapInt64Int64Field, + mapInt32Int32Field, + mapInt32MessageField, + } = proto3_ts.Proto3MessageDesc.field; + assert(mapStringStringField.fieldKind == "map"); + assert(mapInt64Int64Field.fieldKind == "map"); + assert(mapInt32Int32Field.fieldKind == "map"); + assert(mapInt32MessageField.fieldKind == "map"); const n1 = protoInt64.parse(1); const n2 = protoInt64.parse(2); const n3 = protoInt64.parse(3); diff --git a/packages/protobuf-test/src/reflect/reflect.test.ts b/packages/protobuf-test/src/reflect/reflect.test.ts index 92f578c30..6e67e9786 100644 --- a/packages/protobuf-test/src/reflect/reflect.test.ts +++ b/packages/protobuf-test/src/reflect/reflect.test.ts @@ -27,7 +27,7 @@ import { isReflectList, isReflectMap, } from "@bufbuild/protobuf/reflect"; -import { compileMessage, getFieldByLocalName } from "../helpers.js"; +import { compileMessage } from "../helpers.js"; import * as proto3_ts from "../gen/ts/extra/proto3_pb.js"; import * as example_ts from "../gen/ts/extra/example_pb.js"; import assert from "node:assert"; @@ -135,7 +135,8 @@ describe("ReflectMessage", () => { r = reflect(desc, msg); }); test("gets message", () => { - const f = getFieldByLocalName(desc, "singularMessageField", "message"); + const f = desc.field.singularMessageField; + assert(f.fieldKind == "message"); msg.singularMessageField = create(proto3_ts.Proto3MessageDesc); const v = r.get(f); expect(isReflectMessage(v)).toBe(true); @@ -144,27 +145,29 @@ describe("ReflectMessage", () => { } }); test("gets enum", () => { - const f = getFieldByLocalName(desc, "singularEnumField"); + const f = desc.field.singularEnumField; msg.singularEnumField = proto3_ts.Proto3Enum.YES; expect(r.get(f)).toBe(proto3_ts.Proto3Enum.YES); }); test("gets string", () => { - const f = getFieldByLocalName(desc, "singularStringField"); + const f = desc.field.singularStringField; msg.singularStringField = "abc"; expect(r.get(f)).toBe("abc"); }); test("gets list", () => { - const f = getFieldByLocalName(desc, "repeatedStringField", "list"); + const f = desc.field.repeatedStringField; + assert(f.fieldKind == "list"); const list = r.get(f); expect(isReflectList(list)).toBe(true); }); test("gets map", () => { - const f = getFieldByLocalName(desc, "mapStringStringField", "map"); + const f = desc.field.mapStringStringField; + assert(f.fieldKind == "map"); const map = r.get(f); expect(isReflectMap(map)).toBe(true); }); test("gets wrapped wrapper field", () => { - const f = getFieldByLocalName(desc, "singularWrappedUint32Field"); + const f = desc.field.singularWrappedUint32Field; msg.singularWrappedUint32Field = 123; const wrapper = r.get(f); expect(isReflectMessage(wrapper, UInt32ValueDesc)).toBe(true); @@ -174,7 +177,7 @@ describe("ReflectMessage", () => { } }); test("gets selected oneof field", () => { - const f = getFieldByLocalName(desc, "oneofBoolField"); + const f = desc.field.oneofBoolField; msg.either = { case: "oneofBoolField", value: false, @@ -183,19 +186,20 @@ describe("ReflectMessage", () => { }); describe("returns zero value for unset", () => { test("scalar oneof field", () => { - const f = getFieldByLocalName(desc, "oneofBoolField"); + const f = desc.field.oneofBoolField; expect(r.get(f)).toBe(false); }); test("optional string field", () => { - const f = getFieldByLocalName(desc, "optionalStringField"); + const f = desc.field.optionalStringField; expect(r.get(f)).toBe(""); }); test("optional enum field", () => { - const f = getFieldByLocalName(desc, "optionalEnumField"); + const f = desc.field.optionalEnumField; expect(r.get(f)).toBe(proto3_ts.Proto3Enum.UNSPECIFIED); }); test("message field", () => { - const f = getFieldByLocalName(desc, "singularMessageField", "message"); + const f = desc.field.singularMessageField; + assert(f.fieldKind == "message"); const v = r.get(f); expect(isReflectMessage(v)).toBe(true); if (isReflectMessage(v)) { @@ -207,7 +211,8 @@ describe("ReflectMessage", () => { }); }); test("returns ReflectMessage with zero message for unset message field", () => { - const f = getFieldByLocalName(desc, "singularMessageField", "message"); + const f = desc.field.singularMessageField; + assert(f.fieldKind == "message"); const v = r.get(f); expect(isReflectMessage(v)).toBe(true); if (isReflectMessage(v)) { @@ -217,7 +222,7 @@ describe("ReflectMessage", () => { } }); test("returns bigint for jstype=JS_STRING", () => { - const f = getFieldByLocalName(desc, "singularInt64JsStringField"); + const f = desc.field.singularInt64JsStringField; msg.singularInt64JsStringField = "123"; expect(r.get(f)).toBe(protoInt64.parse(123)); }); @@ -242,19 +247,20 @@ describe("ReflectMessage", () => { r = reflect(desc, msg); }); test("sets enum", () => { - const singularEnumField = getFieldByLocalName(desc, "singularEnumField"); - const err = r.set(singularEnumField, proto3_ts.Proto3Enum.YES); + const f = desc.field.singularEnumField; + const err = r.set(f, proto3_ts.Proto3Enum.YES); expect(err).toBeUndefined(); expect(msg.singularEnumField).toBe(proto3_ts.Proto3Enum.YES); }); test("sets string", () => { - const f = getFieldByLocalName(desc, "singularStringField"); + const f = desc.field.singularStringField; const err = r.set(f, "abc"); expect(err).toBeUndefined(); expect(msg.singularStringField).toBe("abc"); }); test("sets ReflectMap", () => { - const f = getFieldByLocalName(desc, "mapStringStringField", "map"); + const f = desc.field.mapStringStringField; + assert(f.fieldKind == "map"); const map = reflectMap(f); expect(map.set("foo", "bar")).toBeUndefined(); const err = r.set(f, map); @@ -262,7 +268,8 @@ describe("ReflectMessage", () => { expect(msg.mapStringStringField).toStrictEqual({ foo: "bar" }); }); test("sets ReflectList", () => { - const f = getFieldByLocalName(desc, "repeatedStringField", "list"); + const f = desc.field.repeatedStringField; + assert(f.fieldKind == "list"); const list = reflectList(f); expect(list.add("foo")).toBeUndefined(); const err = r.set(f, list); @@ -270,72 +277,57 @@ describe("ReflectMessage", () => { expect(msg.repeatedStringField).toStrictEqual(["foo"]); }); test("sets ReflectMessage", () => { - const f = getFieldByLocalName(desc, "singularMessageField"); + const f = desc.field.singularMessageField; const testMessage = create(proto3_ts.Proto3MessageDesc); const err = r.set(f, reflect(proto3_ts.Proto3MessageDesc, testMessage)); expect(err).toBeUndefined(); expect(msg.singularMessageField).toBe(testMessage); }); test("sets number, string, bigint as bigint for 64-bit integer field", () => { - const singularInt64Field = getFieldByLocalName( - desc, - "singularInt64Field", - ); - expect(r.set(singularInt64Field, protoInt64.parse(123))).toBeUndefined(); + const f = desc.field.singularInt64Field; + expect(r.set(f, protoInt64.parse(123))).toBeUndefined(); expect(msg.singularInt64Field === protoInt64.parse(123)).toBe(true); - expect(r.set(singularInt64Field, 123)).toBeUndefined(); + expect(r.set(f, 123)).toBeUndefined(); expect(msg.singularInt64Field === protoInt64.parse(123)).toBe(true); - expect(r.set(singularInt64Field, "123")).toBeUndefined(); + expect(r.set(f, "123")).toBeUndefined(); expect(msg.singularInt64Field === protoInt64.parse(123)).toBe(true); }); test("sets number, string, bigint as string for 64-bit integer field with jstype=JS_STRING", () => { - const singularInt64JsStringField = getFieldByLocalName( - desc, - "singularInt64JsStringField", - ); - expect( - r.set(singularInt64JsStringField, protoInt64.parse(123)), - ).toBeUndefined(); + const f = desc.field.singularInt64JsStringField; + expect(r.set(f, protoInt64.parse(123))).toBeUndefined(); expect(msg.singularInt64JsStringField).toBe("123"); - expect(r.set(singularInt64JsStringField, 123)).toBeUndefined(); + expect(r.set(f, 123)).toBeUndefined(); expect(msg.singularInt64JsStringField).toBe("123"); - expect(r.set(singularInt64JsStringField, "123")).toBeUndefined(); + expect(r.set(f, "123")).toBeUndefined(); expect(msg.singularInt64JsStringField).toBe("123"); }); test("sets unwrapped value for wrapper field", () => { - const singularWrappedUint32Field = getFieldByLocalName( - desc, - "singularWrappedUint32Field", - ); + const f = desc.field.singularWrappedUint32Field; const wrapper = create(UInt32ValueDesc, { value: 123 }); - const err = r.set( - singularWrappedUint32Field, - reflect(UInt32ValueDesc, wrapper), - ); + const err = r.set(f, reflect(UInt32ValueDesc, wrapper)); expect(err).toBeUndefined(); expect(msg.singularWrappedUint32Field).toBe(123); }); test("sets unknown value for open enum", () => { - const singularEnumField = getFieldByLocalName(desc, "singularEnumField"); - const err = r.set(singularEnumField, 99); + const f = desc.field.singularEnumField; + const err = r.set(f, 99); expect(err).toBeUndefined(); expect(msg.singularEnumField).toBe(99); }); test("selects oneof field", () => { - const oneofInt32Field = getFieldByLocalName(desc, "oneofInt32Field"); + const f = desc.field.oneofInt32Field; msg.either = { case: "oneofInt32Field", value: 123, }; - r.set(oneofInt32Field, 123); + r.set(f, 123); expect(msg.either).toStrictEqual({ case: "oneofInt32Field", value: 123, }); }); test("deselects other oneof field", () => { - const oneofBoolField = getFieldByLocalName(desc, "oneofBoolField"); - const oneofInt32Field = getFieldByLocalName(desc, "oneofInt32Field"); + const { oneofBoolField, oneofInt32Field } = desc.field; msg.either = { case: "oneofInt32Field", value: 123, @@ -358,7 +350,7 @@ describe("ReflectMessage", () => { ); }); test("returns error setting number out of range", () => { - const f = getFieldByLocalName(r.desc, "singularInt32Field"); + const f = desc.field.singularInt32Field; const err = r.set(f, Number.MAX_SAFE_INTEGER); expect(err?.message).toMatch( /^expected number \(int32\): 9007199254740991 out of range$/, @@ -366,7 +358,7 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("returns error setting float for int", () => { - const f = getFieldByLocalName(r.desc, "singularInt32Field"); + const f = desc.field.singularInt32Field; const err = r.set(f, 3.142); expect(err?.message).toMatch(/^expected number \(int32\), got 3.142$/); expect(err?.name).toMatch("FieldValueInvalidError"); @@ -452,7 +444,7 @@ describe("ReflectMessage", () => { }); }); test("returns error setting incompatible ReflectMessage", () => { - const f = getFieldByLocalName(r.desc, "singularMessageField"); + const f = desc.field.singularMessageField; const err = r.set(f, reflect(example_ts.UserDesc)); expect(err?.message).toMatch( /^expected ReflectMessage \(spec.Proto3Message\), got ReflectMessage \(docs.User\)$/, @@ -460,16 +452,9 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("returns error setting incompatible ReflectMap", () => { - const mapStringStringField = getFieldByLocalName( - desc, - "mapStringStringField", - "map", - ); - const mapInt32Int32Field = getFieldByLocalName( - desc, - "mapInt32Int32Field", - "map", - ); + const { mapStringStringField, mapInt32Int32Field } = desc.field; + assert(mapStringStringField.fieldKind == "map"); + assert(mapInt32Int32Field.fieldKind == "map"); const map = reflectMap(mapStringStringField); const err = r.set(mapInt32Int32Field, map); expect(err?.message).toMatch( @@ -478,16 +463,9 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("returns error setting incompatible ReflectList", () => { - const repeatedStringField = getFieldByLocalName( - desc, - "repeatedStringField", - "list", - ); - const repeatedInt32Field = getFieldByLocalName( - desc, - "repeatedInt32Field", - "list", - ); + const { repeatedStringField, repeatedInt32Field } = desc.field; + assert(repeatedStringField.fieldKind == "list"); + assert(repeatedInt32Field.fieldKind == "list"); const list = reflectList(repeatedStringField); const err = r.set(repeatedInt32Field, list); expect(err?.message).toMatch( @@ -534,67 +512,27 @@ describe("ReflectMessage", () => { a: "A", }; const r = reflect(desc, msg); - expect(r.isSet(getFieldByLocalName(desc, "singularStringField"))).toBe( - true, - ); - expect(r.isSet(getFieldByLocalName(desc, "singularBytesField"))).toBe( - true, - ); - expect(r.isSet(getFieldByLocalName(desc, "singularInt32Field"))).toBe( - true, - ); - expect(r.isSet(getFieldByLocalName(desc, "singularInt64Field"))).toBe( - true, - ); - expect( - r.isSet(getFieldByLocalName(desc, "singularInt64JsStringField")), - ).toBe(true); - expect(r.isSet(getFieldByLocalName(desc, "singularEnumField"))).toBe( - true, - ); - expect(r.isSet(getFieldByLocalName(desc, "singularMessageField"))).toBe( - true, - ); - expect( - r.isSet(getFieldByLocalName(desc, "singularWrappedUint32Field")), - ).toBe(true); - expect(r.isSet(getFieldByLocalName(desc, "optionalStringField"))).toBe( - true, - ); - expect(r.isSet(getFieldByLocalName(desc, "optionalInt64Field"))).toBe( - true, - ); - expect( - r.isSet(getFieldByLocalName(desc, "optionalInt64JsStringField")), - ).toBe(true); - expect(r.isSet(getFieldByLocalName(desc, "optionalMessageField"))).toBe( - true, - ); - expect( - r.isSet(getFieldByLocalName(desc, "optionalWrappedUint32Field")), - ).toBe(true); - expect(r.isSet(getFieldByLocalName(desc, "repeatedStringField"))).toBe( - true, - ); - expect( - r.isSet(getFieldByLocalName(desc, "repeatedWrappedUint32Field")), - ).toBe(true); - expect(r.isSet(getFieldByLocalName(desc, "repeatedInt64Field"))).toBe( - true, - ); - expect( - r.isSet(getFieldByLocalName(desc, "repeatedInt64JsStringField")), - ).toBe(true); - expect(r.isSet(getFieldByLocalName(desc, "repeatedMessageField"))).toBe( - true, - ); - expect(r.isSet(getFieldByLocalName(desc, "repeatedEnumField"))).toBe( - true, - ); - expect(r.isSet(getFieldByLocalName(desc, "oneofBoolField"))).toBe(true); - expect(r.isSet(getFieldByLocalName(desc, "mapStringStringField"))).toBe( - true, - ); + expect(r.isSet(desc.field.singularStringField)).toBe(true); + expect(r.isSet(desc.field.singularBytesField)).toBe(true); + expect(r.isSet(desc.field.singularInt32Field)).toBe(true); + expect(r.isSet(desc.field.singularInt64Field)).toBe(true); + expect(r.isSet(desc.field.singularInt64JsStringField)).toBe(true); + expect(r.isSet(desc.field.singularEnumField)).toBe(true); + expect(r.isSet(desc.field.singularMessageField)).toBe(true); + expect(r.isSet(desc.field.singularWrappedUint32Field)).toBe(true); + expect(r.isSet(desc.field.optionalStringField)).toBe(true); + expect(r.isSet(desc.field.optionalInt64Field)).toBe(true); + expect(r.isSet(desc.field.optionalInt64JsStringField)).toBe(true); + expect(r.isSet(desc.field.optionalMessageField)).toBe(true); + expect(r.isSet(desc.field.optionalWrappedUint32Field)).toBe(true); + expect(r.isSet(desc.field.repeatedStringField)).toBe(true); + expect(r.isSet(desc.field.repeatedWrappedUint32Field)).toBe(true); + expect(r.isSet(desc.field.repeatedInt64Field)).toBe(true); + expect(r.isSet(desc.field.repeatedInt64JsStringField)).toBe(true); + expect(r.isSet(desc.field.repeatedMessageField)).toBe(true); + expect(r.isSet(desc.field.repeatedEnumField)).toBe(true); + expect(r.isSet(desc.field.oneofBoolField)).toBe(true); + expect(r.isSet(desc.field.mapStringStringField)).toBe(true); }); test("throws error on foreign field", async () => { const foreignMessage = await compileMessage(` @@ -679,19 +617,22 @@ describe("ReflectMessage", () => { r = reflect(desc, msg); }); test("adds valid item to repeatedStringField", () => { - const f = getFieldByLocalName(desc, "repeatedStringField", "list"); + const f = desc.field.repeatedStringField; + assert(f.fieldKind == "list"); const err = r.addListItem(f, "abc"); expect(err).toBeUndefined(); expect(msg.repeatedStringField).toStrictEqual(["abc"]); }); test("adds unknown value for open enum", () => { - const f = getFieldByLocalName(desc, "repeatedEnumField", "list"); + const f = desc.field.repeatedEnumField; + assert(f.fieldKind == "list"); const err = r.addListItem(f, 99); expect(err).toBeUndefined(); expect(msg.repeatedEnumField).toStrictEqual([99]); }); test("adds bigint, number, and string as bigint", () => { - const f = getFieldByLocalName(desc, "repeatedInt64Field", "list"); + const f = desc.field.repeatedInt64Field; + assert(f.fieldKind == "list"); r.addListItem(f, protoInt64.parse(1)); r.addListItem(f, 2); r.addListItem(f, "3"); @@ -702,7 +643,8 @@ describe("ReflectMessage", () => { ]); }); test("adds bigint, number, and string as string for jstype=JS_STRING", () => { - const f = getFieldByLocalName(desc, "repeatedInt64JsStringField", "list"); + const f = desc.field.repeatedInt64JsStringField; + assert(f.fieldKind == "list"); r.addListItem(f, protoInt64.parse(1)); r.addListItem(f, 2); r.addListItem(f, "3"); @@ -721,7 +663,8 @@ describe("ReflectMessage", () => { }); describe("returns error on invalid item", () => { test("bool for repeatedStringField", () => { - const f = getFieldByLocalName(desc, "repeatedStringField", "list"); + const f = desc.field.repeatedStringField; + assert(f.fieldKind == "list"); const err = r.addListItem(f, true); expect(err?.message).toMatch( /^list item #1: expected string, got true$/, @@ -729,7 +672,8 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("number out of range for repeatedInt32Field", () => { - const f = getFieldByLocalName(desc, "repeatedInt32Field", "list"); + const f = desc.field.repeatedInt32Field; + assert(f.fieldKind == "list"); const err = r.addListItem(f, Number.MAX_SAFE_INTEGER); expect(err?.message).toMatch( /^list item #1: expected number \(int32\): 9007199254740991 out of range/, @@ -737,7 +681,8 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("message for repeatedMessageField", () => { - const f = getFieldByLocalName(desc, "repeatedMessageField", "list"); + const f = desc.field.repeatedMessageField; + assert(f.fieldKind == "list"); // @ts-expect-error ignore to test runtime behavior const err = r.addListItem(f, create(example_ts.UserDesc)); expect(err?.message).toMatch( @@ -746,7 +691,8 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("wrong ReflectMessage for repeatedMessageField", () => { - const f = getFieldByLocalName(desc, "repeatedMessageField", "list"); + const f = desc.field.repeatedMessageField; + assert(f.fieldKind == "list"); const testMessage = reflect(example_ts.UserDesc); const err = r.addListItem(f, testMessage); expect(err?.message).toMatch( @@ -755,7 +701,8 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("true for repeatedMessageField", () => { - const f = getFieldByLocalName(desc, "repeatedMessageField", "list"); + const f = desc.field.repeatedMessageField; + assert(f.fieldKind == "list"); const err = r.addListItem(f, true); expect(err?.message).toMatch( /^list item #1: expected ReflectMessage \(spec.Proto3Message\), got true/, @@ -773,7 +720,8 @@ describe("ReflectMessage", () => { r = reflect(desc, msg); }); test("adds valid entry to mapStringStringField", () => { - const f = getFieldByLocalName(desc, "mapStringStringField", "map"); + const f = desc.field.mapStringStringField; + assert(f.fieldKind == "map"); const err = r.setMapEntry(f, "key", "value"); expect(err).toBeUndefined(); expect(msg.mapStringStringField).toStrictEqual({ key: "value" }); @@ -792,7 +740,8 @@ describe("ReflectMessage", () => { ); }); test("adds bigint, number, and string value as bigint", () => { - const f = getFieldByLocalName(desc, "mapInt64Int64Field", "map"); + const f = desc.field.mapInt64Int64Field; + assert(f.fieldKind == "map"); expect( r.setMapEntry(f, protoInt64.parse(1), protoInt64.parse(1)), ).toBeUndefined(); @@ -805,7 +754,8 @@ describe("ReflectMessage", () => { ]); }); test("adds bigint, number, and string key as string", () => { - const f = getFieldByLocalName(desc, "mapInt64Int64Field", "map"); + const f = desc.field.mapInt64Int64Field; + assert(f.fieldKind == "map"); expect( r.setMapEntry(f, protoInt64.parse(1), protoInt64.parse(1)), ).toBeUndefined(); @@ -818,7 +768,8 @@ describe("ReflectMessage", () => { ]); }); test("adds bool key as string", () => { - const f = getFieldByLocalName(desc, "mapBoolBoolField", "map"); + const f = desc.field.mapBoolBoolField; + assert(f.fieldKind == "map"); expect(r.setMapEntry(f, true, true)).toBeUndefined(); expect(r.setMapEntry(f, false, false)).toBeUndefined(); expect(Object.keys(msg.mapBoolBoolField)).toStrictEqual([ @@ -828,7 +779,8 @@ describe("ReflectMessage", () => { }); describe("returns error on invalid value", () => { test("wrong message", () => { - const f = getFieldByLocalName(desc, "mapInt32MessageField", "map"); + const f = desc.field.mapInt32MessageField; + assert(f.fieldKind == "map"); const err = r.setMapEntry(f, 123, reflect(example_ts.UserDesc)); expect(err?.message).toMatch( /^map entry 123: expected ReflectMessage \(spec.Proto3Message\), got ReflectMessage \(docs.User\)/, @@ -836,7 +788,8 @@ describe("ReflectMessage", () => { expect(err?.name).toMatch("FieldValueInvalidError"); }); test("number out of range", () => { - const f = getFieldByLocalName(desc, "mapInt32Int32Field", "map"); + const f = desc.field.mapInt32Int32Field; + assert(f.fieldKind == "map"); const err = r.setMapEntry(f, 123, Number.MAX_SAFE_INTEGER); expect(err?.message).toMatch( /^map entry 123: expected number \(int32\): 9007199254740991 out of range/, @@ -846,7 +799,8 @@ describe("ReflectMessage", () => { }); describe("returns error on invalid key", () => { test("number out of range", () => { - const f = getFieldByLocalName(desc, "mapInt32Int32Field", "map"); + const f = desc.field.mapInt32Int32Field; + assert(f.fieldKind == "map"); const err = r.setMapEntry(f, Number.MAX_SAFE_INTEGER, 123); expect(err?.message).toMatch( /^invalid map key: expected number \(int32\): 9007199254740991 out of range/, diff --git a/packages/protobuf-test/src/reflect/registry.test.ts b/packages/protobuf-test/src/reflect/registry.test.ts index 608d4f50a..70b220e81 100644 --- a/packages/protobuf-test/src/reflect/registry.test.ts +++ b/packages/protobuf-test/src/reflect/registry.test.ts @@ -543,6 +543,45 @@ describe("DescFile", () => { }); }); +describe("DescMessage", () => { + describe("deprecated", () => { + test("is false by default", async () => { + const descMessage = await compileMessage(` + syntax="proto3"; + option deprecated = true; + message Foo {} + `); + expect(descMessage.deprecated).toBe(false); + }); + test("is true with option", async () => { + const descMessage = await compileMessage(` + syntax="proto3"; + message Foo { + option deprecated = true; + } + `); + expect(descMessage.deprecated).toBe(true); + }); + }); + describe("field", () => { + test("contains field by localName", async () => { + const descMessage = await compileMessage(` + syntax="proto3"; + message Foo { + int32 foo_bar = 1; + oneof kind { + int32 oneof_field = 2; + } + } + `); + expect(Object.keys(descMessage.field).sort()).toStrictEqual([ + "fooBar", + "oneofField", + ]); + }); + }); +}); + describe("DescEnum", () => { describe("open", () => { test("proto3 enum is open", async () => { diff --git a/packages/protobuf/src/codegenv1/embed.ts b/packages/protobuf/src/codegenv1/embed.ts index 2f1b4dbc0..55cf5dafd 100644 --- a/packages/protobuf/src/codegenv1/embed.ts +++ b/packages/protobuf/src/codegenv1/embed.ts @@ -71,8 +71,8 @@ export function embedFileDesc( bootable: false, proto() { const stripped = clone(FileDescriptorProtoDesc, file); - clearField(FileDescriptorProtoDesc, stripped, "dependency"); - clearField(FileDescriptorProtoDesc, stripped, "sourceCodeInfo"); + clearField(stripped, FileDescriptorProtoDesc.field.dependency); + clearField(stripped, FileDescriptorProtoDesc.field.sourceCodeInfo); stripped.messageType.map(stripJsonNames); return stripped; }, @@ -95,7 +95,7 @@ export function embedFileDesc( function stripJsonNames(d: DescriptorProto): void { for (const f of d.field) { if (f.jsonName === protoCamelCase(f.name)) { - clearField(FieldDescriptorProtoDesc, f, "jsonName"); + clearField(f, FieldDescriptorProtoDesc.field.jsonName); } } for (const n of d.nestedType) { @@ -214,12 +214,12 @@ function createDescriptorBoot(proto: DescriptorProto) { function createFieldDescriptorBoot( proto: FieldDescriptorProto, ): FieldDescriptorProtoBoot { - assert(isFieldSet(FieldDescriptorProtoDesc, proto, "name")); - assert(isFieldSet(FieldDescriptorProtoDesc, proto, "number")); - assert(isFieldSet(FieldDescriptorProtoDesc, proto, "type")); - assert(!isFieldSet(FieldDescriptorProtoDesc, proto, "oneofIndex")); + assert(isFieldSet(proto, FieldDescriptorProtoDesc.field.name)); + assert(isFieldSet(proto, FieldDescriptorProtoDesc.field.number)); + assert(isFieldSet(proto, FieldDescriptorProtoDesc.field.type)); + assert(!isFieldSet(proto, FieldDescriptorProtoDesc.field.oneofIndex)); assert( - !isFieldSet(FieldDescriptorProtoDesc, proto, "jsonName") || + !isFieldSet(proto, FieldDescriptorProtoDesc.field.jsonName) || proto.jsonName === protoCamelCase(proto.name), ); const b: FieldDescriptorProtoBoot = { @@ -227,16 +227,16 @@ function createFieldDescriptorBoot( number: proto.number, type: proto.type, }; - if (isFieldSet(FieldDescriptorProtoDesc, proto, "label")) { + if (isFieldSet(proto, FieldDescriptorProtoDesc.field.label)) { b.label = proto.label; } - if (isFieldSet(FieldDescriptorProtoDesc, proto, "typeName")) { + if (isFieldSet(proto, FieldDescriptorProtoDesc.field.typeName)) { b.typeName = proto.typeName; } - if (isFieldSet(FieldDescriptorProtoDesc, proto, "extendee")) { + if (isFieldSet(proto, FieldDescriptorProtoDesc.field.extendee)) { b.extendee = proto.extendee; } - if (isFieldSet(FieldDescriptorProtoDesc, proto, "defaultValue")) { + if (isFieldSet(proto, FieldDescriptorProtoDesc.field.defaultValue)) { b.defaultValue = proto.defaultValue; } if (proto.options) { @@ -247,19 +247,19 @@ function createFieldDescriptorBoot( function createFieldOptionsBoot(proto: FieldOptions): FieldOptionsBoot { const b: FieldOptionsBoot = {}; - assert(!isFieldSet(FieldOptionsDesc, proto, "ctype")); - if (isFieldSet(FieldOptionsDesc, proto, "packed")) { + assert(!isFieldSet(proto, FieldOptionsDesc.field.ctype)); + if (isFieldSet(proto, FieldOptionsDesc.field.packed)) { b.packed = proto.packed; } - assert(!isFieldSet(FieldOptionsDesc, proto, "jstype")); - assert(!isFieldSet(FieldOptionsDesc, proto, "lazy")); - assert(!isFieldSet(FieldOptionsDesc, proto, "unverifiedLazy")); - if (isFieldSet(FieldOptionsDesc, proto, "deprecated")) { + assert(!isFieldSet(proto, FieldOptionsDesc.field.jstype)); + assert(!isFieldSet(proto, FieldOptionsDesc.field.lazy)); + assert(!isFieldSet(proto, FieldOptionsDesc.field.unverifiedLazy)); + if (isFieldSet(proto, FieldOptionsDesc.field.deprecated)) { b.deprecated = proto.deprecated; } - assert(!isFieldSet(FieldOptionsDesc, proto, "weak")); - assert(!isFieldSet(FieldOptionsDesc, proto, "debugRedact")); - if (isFieldSet(FieldOptionsDesc, proto, "retention")) { + assert(!isFieldSet(proto, FieldOptionsDesc.field.weak)); + assert(!isFieldSet(proto, FieldOptionsDesc.field.debugRedact)); + if (isFieldSet(proto, FieldOptionsDesc.field.retention)) { b.retention = proto.retention; } if (proto.targets.length) { @@ -273,8 +273,8 @@ function createFieldOptionsBoot(proto: FieldOptions): FieldOptionsBoot { }), ); } - assert(!isFieldSet(FieldOptionsDesc, proto, "features")); - assert(!isFieldSet(FieldOptionsDesc, proto, "uninterpretedOption")); + assert(!isFieldSet(proto, FieldOptionsDesc.field.features)); + assert(!isFieldSet(proto, FieldOptionsDesc.field.uninterpretedOption)); return b; } diff --git a/packages/protobuf/src/codegenv1/types.ts b/packages/protobuf/src/codegenv1/types.ts index 7fc91b453..c77e7e74f 100644 --- a/packages/protobuf/src/codegenv1/types.ts +++ b/packages/protobuf/src/codegenv1/types.ts @@ -16,6 +16,7 @@ import type { Message } from "../types.js"; import type { DescEnum, DescExtension, + DescField, DescFile, DescMessage, DescService, @@ -36,8 +37,11 @@ export type GenDescFile = DescFile; * * @private */ -export type GenDescMessage = DescMessage & - brand; +// prettier-ignore +export type GenDescMessage = + & Omit + & { field: Record, DescField> } + & brand; /** * Describes an enumeration in a protobuf source file. @@ -88,3 +92,22 @@ class brand { protected a: A | boolean = false; protected b: B | boolean = false; } + +/** + * Union of the property names of all fields, including oneof members. + * For an anonymous message (no generated message shape), it's simply a string. + */ +// prettier-ignore +type MessageFieldNames = Message extends T ? string : + Exclude ? K + : P + ]-?: true; + }, number | symbol>; + +type Oneof = { + case: K | undefined; + value?: unknown; +}; diff --git a/packages/protobuf/src/desc-types.ts b/packages/protobuf/src/desc-types.ts index 8dd6045d9..9db0f043d 100644 --- a/packages/protobuf/src/desc-types.ts +++ b/packages/protobuf/src/desc-types.ts @@ -209,6 +209,10 @@ export interface DescMessage { * group. */ readonly fields: DescField[]; + /** + * All fields of this message by their "localName". + */ + readonly field: Record; /** * Oneof groups declared for this message. * This does not include synthetic oneofs for proto3 optionals. diff --git a/packages/protobuf/src/fields.ts b/packages/protobuf/src/fields.ts index 028591942..1288fcd26 100644 --- a/packages/protobuf/src/fields.ts +++ b/packages/protobuf/src/fields.ts @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { Message, MessageShape } from "./types.js"; +import type { MessageShape } from "./types.js"; +import type { DescField, DescMessage } from "./desc-types.js"; import { unsafeClear, unsafeIsSet } from "./reflect/unsafe.js"; -import type { DescMessage } from "./desc-types.js"; /** * Returns true if the field is set. @@ -33,46 +33,22 @@ import type { DescMessage } from "./desc-types.js"; * Set if not empty. */ export function isFieldSet( - messageDesc: Desc, message: MessageShape, - fieldName: MessageFieldNames>, + field: DescField, ): boolean { - const field = messageDesc.fields.find((f) => f.localName === fieldName); - if (field) { - return unsafeIsSet(message, field); - } - return false; + return ( + field.parent.typeName == message.$typeName && unsafeIsSet(message, field) + ); } /** * Resets the field, so that isFieldSet() will return false. */ export function clearField( - messageDesc: Desc, message: MessageShape, - fieldName: MessageFieldNames>, + field: DescField, ): void { - const field = messageDesc.fields.find((f) => f.localName === fieldName); - if (field) { + if (field.parent.typeName == message.$typeName) { unsafeClear(message, field); } } - -/** - * Union of the property names of all fields, including oneof members. - * For an anonymous message (no generated message shape), it's simply a string. - */ -// prettier-ignore -type MessageFieldNames = Message extends T ? string : - Exclude ? K - : P - ]-?: true; -}, number | symbol>; - -type Oneof = { - case: K | undefined; - value?: unknown; -}; diff --git a/packages/protobuf/src/reflect/registry.ts b/packages/protobuf/src/reflect/registry.ts index 926ae3690..0f701ff6d 100644 --- a/packages/protobuf/src/reflect/registry.ts +++ b/packages/protobuf/src/reflect/registry.ts @@ -543,6 +543,7 @@ function addFields( const oneof = findOneof(proto, allOneofs); const field = newField(proto, message, reg, oneof, mapEntries); message.fields.push(field); + message.field[field.localName] = field; if (oneof === undefined) { message.members.push(field); } else { @@ -629,6 +630,7 @@ function addMessage( name: proto.name, typeName: makeTypeName(proto, parent, file), fields: [], + field: {}, oneofs: [], members: [], nestedEnums: [], diff --git a/packages/protoplugin/src/source-code-info.ts b/packages/protoplugin/src/source-code-info.ts index f4209f784..c036cdd5c 100644 --- a/packages/protoplugin/src/source-code-info.ts +++ b/packages/protoplugin/src/source-code-info.ts @@ -36,6 +36,10 @@ import { FieldOptionsDesc, FeatureSetDesc, SourceCodeInfo_LocationDesc, + FileDescriptorProtoDesc, + DescriptorProtoDesc, + EnumDescriptorProtoDesc, + ServiceDescriptorProtoDesc, } from "@bufbuild/protobuf/wkt"; /** @@ -43,7 +47,7 @@ import { */ export function getPackageComments(desc: DescFile): DescComments { return findComments(desc.proto.sourceCodeInfo, [ - FieldNumber.FileDescriptorProto_Package, + FileDescriptorProtoDesc.field.package.number, ]); } @@ -52,7 +56,7 @@ export function getPackageComments(desc: DescFile): DescComments { */ export function getSyntaxComments(desc: DescFile): DescComments { return findComments(desc.proto.sourceCodeInfo, [ - FieldNumber.FileDescriptorProto_Syntax, + FileDescriptorProtoDesc.field.syntax.number, ]); } @@ -67,11 +71,11 @@ export function getComments(desc: Exclude): DescComments { path = desc.parent ? [ ...getComments(desc.parent).sourcePath, - FieldNumber.DescriptorProto_EnumType, + DescriptorProtoDesc.field.enumType.number, desc.parent.proto.enumType.indexOf(desc.proto), ] : [ - FieldNumber.FileDescriptorProto_EnumType, + FileDescriptorProtoDesc.field.enumType.number, desc.file.proto.enumType.indexOf(desc.proto), ]; file = desc.file; @@ -79,7 +83,7 @@ export function getComments(desc: Exclude): DescComments { case "oneof": path = [ ...getComments(desc.parent).sourcePath, - FieldNumber.DescriptorProto_OneofDecl, + DescriptorProtoDesc.field.oneofDecl.number, desc.parent.proto.oneofDecl.indexOf(desc.proto), ]; file = desc.parent.file; @@ -88,11 +92,11 @@ export function getComments(desc: Exclude): DescComments { path = desc.parent ? [ ...getComments(desc.parent).sourcePath, - FieldNumber.DescriptorProto_NestedType, + DescriptorProtoDesc.field.nestedType.number, desc.parent.proto.nestedType.indexOf(desc.proto), ] : [ - FieldNumber.FileDescriptorProto_MessageType, + FileDescriptorProtoDesc.field.messageType.number, desc.file.proto.messageType.indexOf(desc.proto), ]; file = desc.file; @@ -100,7 +104,7 @@ export function getComments(desc: Exclude): DescComments { case "enum_value": path = [ ...getComments(desc.parent).sourcePath, - FieldNumber.EnumDescriptorProto_Value, + EnumDescriptorProtoDesc.field.value.number, desc.parent.proto.value.indexOf(desc.proto), ]; file = desc.parent.file; @@ -108,7 +112,7 @@ export function getComments(desc: Exclude): DescComments { case "field": path = [ ...getComments(desc.parent).sourcePath, - FieldNumber.DescriptorProto_Field, + DescriptorProtoDesc.field.field.number, desc.parent.proto.field.indexOf(desc.proto), ]; file = desc.parent.file; @@ -117,18 +121,18 @@ export function getComments(desc: Exclude): DescComments { path = desc.parent ? [ ...getComments(desc.parent).sourcePath, - FieldNumber.DescriptorProto_Extension, + DescriptorProtoDesc.field.extension.number, desc.parent.proto.extension.indexOf(desc.proto), ] : [ - FieldNumber.FileDescriptorProto_Extension, + FileDescriptorProtoDesc.field.extension.number, desc.file.proto.extension.indexOf(desc.proto), ]; file = desc.file; break; case "service": path = [ - FieldNumber.FileDescriptorProto_Service, + FileDescriptorProtoDesc.field.service.number, desc.file.proto.service.indexOf(desc.proto), ]; file = desc.file; @@ -136,7 +140,7 @@ export function getComments(desc: Exclude): DescComments { case "rpc": path = [ ...getComments(desc.parent).sourcePath, - FieldNumber.ServiceDescriptorProto_Method, + ServiceDescriptorProtoDesc.field.method.number, desc.parent.proto.method.indexOf(desc.proto), ]; file = desc.parent.file; @@ -219,11 +223,11 @@ export function getDeclarationString( const protoOptions = desc.proto.options; if ( protoOptions !== undefined && - isFieldSet(FieldOptionsDesc, protoOptions, "packed") + isFieldSet(protoOptions, FieldOptionsDesc.field.packed) ) { options.push(`packed = ${protoOptions.packed.toString()}`); } - if (isFieldSet(FieldDescriptorProtoDesc, desc.proto, "defaultValue")) { + if (isFieldSet(desc.proto, FieldDescriptorProtoDesc.field.defaultValue)) { let defaultValue = desc.proto.defaultValue; if ( desc.proto.type == FieldDescriptorProto_Type.BYTES || @@ -238,13 +242,13 @@ export function getDeclarationString( } if ( protoOptions !== undefined && - isFieldSet(FieldOptionsDesc, protoOptions, "jstype") + isFieldSet(protoOptions, FieldOptionsDesc.field.jstype) ) { options.push(`jstype = ${FieldOptions_JSType[protoOptions.jstype]}`); } if ( protoOptions !== undefined && - isFieldSet(FieldOptionsDesc, protoOptions, "deprecated") + isFieldSet(protoOptions, FieldOptionsDesc.field.deprecated) ) { options.push(`deprecated = true`); } @@ -309,16 +313,14 @@ function findComments( return { leadingDetached: location.leadingDetachedComments, leading: isFieldSet( - SourceCodeInfo_LocationDesc, location, - "leadingComments", + SourceCodeInfo_LocationDesc.field.leadingComments, ) ? location.leadingComments : undefined, trailing: isFieldSet( - SourceCodeInfo_LocationDesc, location, - "trailingComments", + SourceCodeInfo_LocationDesc.field.trailingComments, ) ? location.trailingComments : undefined, @@ -330,23 +332,3 @@ function findComments( sourcePath, }; } - -/** - * The following field numbers are used to find comments in - * google.protobuf.SourceCodeInfo. - */ -enum FieldNumber { - FileDescriptorProto_Package = 2, - FileDescriptorProto_MessageType = 4, - FileDescriptorProto_EnumType = 5, - FileDescriptorProto_Service = 6, - FileDescriptorProto_Extension = 7, - FileDescriptorProto_Syntax = 12, - DescriptorProto_Field = 2, // eslint-disable-line @typescript-eslint/no-duplicate-enum-values - DescriptorProto_NestedType = 3, - DescriptorProto_EnumType = 4, // eslint-disable-line @typescript-eslint/no-duplicate-enum-values - DescriptorProto_Extension = 6, // eslint-disable-line @typescript-eslint/no-duplicate-enum-values - DescriptorProto_OneofDecl = 8, - EnumDescriptorProto_Value = 2, // eslint-disable-line @typescript-eslint/no-duplicate-enum-values - ServiceDescriptorProto_Method = 2, // eslint-disable-line @typescript-eslint/no-duplicate-enum-values -}