From b565ff57c0a64a3869ba7475c2b53f46504169d0 Mon Sep 17 00:00:00 2001 From: Elias Kassell Date: Tue, 2 May 2023 15:07:16 +0100 Subject: [PATCH] feat: Add type annotations flag (#786) * Add type annotations flag * Add missing newline * Fix timestamp type adding * Undo package json change --- README.markdown | 4 +- integration/type-annotations/bar/bar.bin | Bin 0 -> 11628 bytes integration/type-annotations/bar/bar.proto | 9 + integration/type-annotations/bar/bar.ts | 83 +++ integration/type-annotations/foo.bin | Bin 0 -> 11423 bytes integration/type-annotations/foo.proto | 18 + integration/type-annotations/foo.ts | 236 ++++++++ .../google/protobuf/struct.ts | 553 ++++++++++++++++++ .../google/protobuf/timestamp.ts | 235 ++++++++ integration/type-annotations/parameters.txt | 1 + .../type-annotations/type-registry-test.ts | 35 ++ src/main.ts | 33 +- src/options.ts | 2 + tests/options-test.ts | 1 + 14 files changed, 1198 insertions(+), 12 deletions(-) create mode 100644 integration/type-annotations/bar/bar.bin create mode 100644 integration/type-annotations/bar/bar.proto create mode 100644 integration/type-annotations/bar/bar.ts create mode 100644 integration/type-annotations/foo.bin create mode 100644 integration/type-annotations/foo.proto create mode 100644 integration/type-annotations/foo.ts create mode 100644 integration/type-annotations/google/protobuf/struct.ts create mode 100644 integration/type-annotations/google/protobuf/timestamp.ts create mode 100755 integration/type-annotations/parameters.txt create mode 100644 integration/type-annotations/type-registry-test.ts diff --git a/README.markdown b/README.markdown index 40f363854..3f43bc0a9 100644 --- a/README.markdown +++ b/README.markdown @@ -414,7 +414,9 @@ Generated code will be placed in the Gradle build directory. - With `--ts_proto_opt=outputSchema=true`, meta typings will be generated that can later be used in other code generators. -- With `--ts_proto_opt=outputTypeRegistry=true`, the type registry will be generated that can be used to resolve message types by fully-qualified name. Also, each message will get extra `$type` field containing fully-qualified name. +- With `--ts_proto_opt=outputTypeAnnotations=true`, each message will be given a `$type` field containing its fully-qualified name. + +- With `--ts_proto_opt=outputTypeRegistry=true`, the type registry will be generated that can be used to resolve message types by fully-qualified name. Also, each message will be given a `$type` field containing its fully-qualified name. - With `--ts_proto_opt=outputServices=grpc-js`, ts-proto will output service definitions and server / client stubs in [grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) format. diff --git a/integration/type-annotations/bar/bar.bin b/integration/type-annotations/bar/bar.bin new file mode 100644 index 0000000000000000000000000000000000000000..3aa6c75d1a622dc07f87f1b8ec946720f12be2bc GIT binary patch literal 11628 zcmeHN%WoS=dgn_LRg!I)p6odEtCk*($pJ;`VSC2%3?>xSl4w(;mJe%f!x=TnqSWhV zH`CpeEIUSk0Kpy>2(UQ?L4pPL5Fj}wha7SWu$x>K*jtXd<$uU6`F&O0BxOl;G6{xAtv&-xPT{)f#m3p3xJo zr}z2_I&nz>s5bzl9tsHkFvT8TzYmnEhs!g=Z7do1w(=zs+*FqHUXe zZP4us$K?xL;kx7Uj_&E)usq?k4?vXQUEAsD9^*y3f9x3h2OeKuT>Q#kz)My;$9cgt zxtw#kBV6GeicXI41JCQbtNDCK917F!W1~PO5Rc*%L@a=GA|lTiuZfP~dXCW=c!q6p z-RkgxD?ko+?Sa!4a-?Ngx^pb)aMKvCQ72;LjG9ys@O zP)beX76bRAg!s=iYh#VrKXM3;!DH9LY z4eDT=yo(72ywHYgcfBJr6CxBA#NEDVlLcXwL5Ag!?O4i+To(qCgIP4TN_AeZZZvia zHI3tWr&fJZTG!V3+EYwwyjb0NS}SdCHTYJwysp*iyii%kXr)mrtu-6fTAlG!p^l{~ zIa8=S<=T&TYFfR{t2JKQ-YJ)W4d`oyN~5IJv%FL(mYeIP%4U`W1FuvYjF(H>r3T;{ z)vVn1^g3SM;M-cQxCJzYwNkm%cq%vCC^agy*+#X-c!BQ}YK>B{SuWK0PP4XCt!tb( zTQAj%OVUn*GwhNnDou@w}6)2|ItZCas zHAGQwuGJf*Mzf*u&1!XB3cIe=o|KAO{UI+`N&FkUS=T_wdZAH}TLTBgg?TWr)~uJL z;7XN-R;x948l`F_11;}DGGM)cwd+#C)e3P-wxU&QPYEr_L24k&cegZ*L(vr_tpW+8 z4&x{`#=)>5Yz2xQ@yRRNX1TPfRf-zXQzbOJrMi}Z!b+fRlW>xlcMI4QhJuaBB0(2a zM9*WU3n4?FytKg!>ra4WJ%EM<)Jwj(U{SqU-121(^N+J|BoXDItMn94L{lN$m@E>X zSC4A&cIcKIyp@R3qZ<4yRFZ=;i6}j)!TC^D4u%s^dQ^j-hw^d|=;=`n-U+4TAWKB) zQ4OX;Ka_)?C8G5BUpH7J7D`P1G)z}t^0zl&-NUOFUb;_Llm%y?^S+InPP#QXTKw&Z zKD-Ae$qVV$F{em@zqT#uO-#G3oA5ol2_W6c!qbrV?{J-Pwe5jg=LZY&uIf5^Tiq#i zW2h*C(`_o_2Ki1mrftd#FUK43f(j8llOs&HSX?Af}Cyk~SQc^TUl zO-Jn8aLm?_dVX|Z0G)UR-_~)-#j>-YT6+5> zydy!_{8;F4^p+q8NLPN(um+xx_a(o-IONZ?5zO$i(EGz7@~l1w_YM+oR=bYN&g2FnlC%s)sJ0I^crBM;BFYEpro9^F*gxW7%{tZv3(`qEs^>Uq~EjW&K7!B=|;a`8vSC%vUPq6{!%Jxw(704~0 zhXVz@9s3Ag04V8%g4Hn}=H`X~%1bTXKYfgp7J!YGFWtX;_wJV~_kkvGi!FikE_l;2 zUirR`oY}jF(GMJnc4?K;3_fh1xIQ6~3`C)Xl9$SYz7{jce#Xf8fCUpC5?6p{ehiN^ z0J8s)iQhff03PXwI^R>u&$S1RgIkA~+BQt&2V(|6-V~meMxn~{7X<1$Lo!%59L-tc zXl-Dah=kJ_I>L2zE2aG{lwdMSS#)d&rb1b6It^b;ocoZ9^^9-#B*{#!MVQkhf{_gF zeLw~!3+l7H95UB2w*67u5l0~Z1k8Im9TEOvmVrA5r=iiss>G1ajB$RFdmorY0jM)M zy?-vJzJjH(((Sx+j9fz|610goH0CiyrF}|b`iPrm+99bx9S-tWWxW1wWI z5tX%5!a45nJroA$_?-w`)Yaj~3`|}?ztjDrwxZYtSGs|eT*eY=lr~Gfp}67g!kZqF zwgdcyH-FoBLznm4)i;PC@IQRO+vmAA6fTj+ef#2BiXlP~@CRx&A=1qw{TRf*q6m(X zJrxUdDGKNq`$!Bas6hnb@08_oD+KatGrZGoC<@@Rm0Qa4z-!^y3w^yK>jJp8kL&=O z(Ax(pCJW*?ivK_#6@zf8%Ls}t@_m~R`Z7!imZpuI$oYdyW1ey>+RK12ii>PSXtt%B zYNymQ#7C%zzzM?TQXK;U7HN^}Kt`hJ6av6h=HZD^Bi{Vgq4HCM(pR=E6Ny9 zkwH#wfKl#3>?R^sB}yu;P&AoF1RLzu;Vr~6AR|KxK;DbMLWMM^O^F1FD0*zGzK+y; zxJ?T|rHtt)!fCe=i}Dr<1#(YXtBM8zXYMIFDD1VnU3efFu5BP56e_mK^3?L;(wB5Q zEZu7?F0HOCt}flnEiOTP%DQpM(0iaR-q&5k-4asnkNVOuZoZdAQETZ-3V!GS!8O<0 zj?wqB6z!e})pSnRBez>W5fp+@ln254#;)v9nm@TTN~sBYSRiuqdf$)=A)aVdvXfUj&!5IE`6S8G2J_47_+>mpg0x$*La6(E zBC&yO_2mIUdblqv;ppn9VWo^|=va=M7%Y^r85WI&!im^V!|}`P@4_q=3q?YS*w4a| zPm+Hb=Cx6V>$@t-X6cv{HyrsBs)M5_i^-hvRG7uLsip~Acwbt?&dQ6Pf#$Y}vv5d> z;srKAc|FdxXQt%q3*Tfa}--;Q`{u~8{B0c+RF))1$Yk}_^>7CHfsSpg@jWGKe<9fxc2fyD^iPSg zR1oBvdq-gMHhAzLiv*GWQStO$p@bn6@EHgtEZL4<@P!hFP<|esyWE;o{`K|$rr34W zFc@_SaFUP;o!7?r>oA*y;{inCA)D+P!jwIXXnJP(c2J6!$fN<(?|xaKcO z{)o+t4JYPU^rbkK&5-FDJtnejLW)*4LS~kKa0>Yo5`0Yotwz2Ir~WCzCgdO}W7fbl zzf%aP!*X#sc?v-VAQFBnRGTJvwfHuhwFbSGaQx*FEDr-@MvV@EASc8}K2Q`u(*&eO z70@llTDBdK5d%n^wxTfw@HLwSJY?x(nE=Sl@~5XLRdV#v16-{JmsIxm*G|f{r@1Tx zM8eb71TSCKCRywyu4igX4IvUaHhCz`8Hyl{o+or4>Wgb^dML!iMaC#M{_aVk+|=em zYkzij%W6h3v*Y)x^2+?c^85Q$N>0C1v+8edu*pR1X6TPU_NqSis#Kft`}E8{_Nscu z$6nRPURCpBuL?b)-@8|}A^S?QSGDo}UX^NK$zD~^!2196s{T1DdsVUJ(7kZ-@1rOP z$`E4@sr*=*O5LaE9(2^_8S>uGQkLc-Mxb^B0(=$TQPqU{Iz2RBka2X5(Njke!8Fm! zLh+~LN3t?CbZw*qbdwMCeSvyLusQNa*;+#I?+*HX+o4Y5qfhfvpd29+QCYv21q+``Y(PykELEAcs)cmuiwb;80Pa*XB@l|- zR~An?g7#`*y=SA8hl&v-K%azAPAOyCfCt%4qS1gP%Jv(0mld>=EJ@fv98_76Fg|IN zg2@Cdx*WgA9{A~3^lpSFv&gG#2c=ceLmB|#=W2klS(O@1Cb0IC;0-;{yMJ&kc#Gog z<}LQC2+RcO!8f7paPnUwd{4%O?^ww>JB^j8piuR-1^SN&p{XdK;$&4Gpb(j!DCQM9 zeLMp*MCFn)IchK=m{sP3gxldd8-B>5;!wZpL?54^OW0MQBs>)CP%M!uLe7TXDz#Hd zrRc*lfKgx4LkpqMLSGiSDdwp6>Q5kX3d&$8P6)@b@kPuBH0O&aP@JR>i%4(CvZCK0 z$x1kwvo#s zA?URbyP%Ln2ub1M)!+?!EnK_KG7=@Gim~g->nBlm>;ZK{Pa;Li2w0!82=P*!`P4^> zgfUu7Uh|Q{=V_;rLW^`NPa=%~sWwC!0a9&wYVyWON_~5I2Wu2q zH)C<1xG1o0PG1S$2+x55pHVa z!rUjw?&=d4S(3#js7e%VMlL5Wa9TF5FPZ~nGcrpWqBq!2^U@sqbMPCBBx28znI#YT z-Z204**$X5dcb+t9en;k#o(cI<$W|fO|monUT=F6v*bi~9R1iow`7*jQSVxX$wY{e z7m@491u_|>%;9$DxIrR-4kp&Z-d;q8Z&3P+nYmyNyS%upP81-a^cTr%!wSs5#@KY% zw*7KT6pzUdeOsD*Z^~$@{@IynKMctR8Eu-wGnXYk5~){mcIvv zZy4Lfg;`g1qCA^a2hS(ty!uAX2Yd932qo^J(B&`%NchGy6iUz&>?abMrT8fdWyL}n z`3Uh&gg%F5(ib|hsnB#-I%TrGsrU>ko2r%>jYqza8i64~Kk>-$LIhy^`g|}4{luvc zrf$cX&;^B#{NfBeHldRqd)7yX7oeN<(cuk%vpzb!0o|;R4kmwzc%mzVOz%=WA<;?G zn~QuF*bC6jMU-2|6aeS`@K!JfbaScOL+knXFgy8`a$<^%siix~kyWLB$f9eylSnc$ zFJB4q905YlH5z<$s3aagOX71QiC+oCJQ)IL5VK5>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.foo = Foo.decode(reader, reader.uint32()); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Bar { + return { $type: Bar.$type, foo: isSet(object.foo) ? Foo.fromJSON(object.foo) : undefined }; + }, + + toJSON(message: Bar): unknown { + const obj: any = {}; + message.foo !== undefined && (obj.foo = message.foo ? Foo.toJSON(message.foo) : undefined); + return obj; + }, + + create, I>>(base?: I): Bar { + return Bar.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Bar { + const message = createBaseBar(); + message.foo = (object.foo !== undefined && object.foo !== null) ? Foo.fromPartial(object.foo) : undefined; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in Exclude]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude | "$type">]: never }; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/type-annotations/foo.bin b/integration/type-annotations/foo.bin new file mode 100644 index 0000000000000000000000000000000000000000..85b788882ef5ac58f3b7b5fde3a4e6118cfb0384 GIT binary patch literal 11423 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 bdjY!nm~!iw0^t1b-wfw~Za#f0w4VP1h*%F6 literal 0 HcmV?d00001 diff --git a/integration/type-annotations/foo.proto b/integration/type-annotations/foo.proto new file mode 100644 index 000000000..a4d68c826 --- /dev/null +++ b/integration/type-annotations/foo.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package foo; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/struct.proto"; + +message Foo { + google.protobuf.Timestamp timestamp = 1; +} + +message Foo2 { + google.protobuf.Timestamp timestamp = 1; +} + +message WithStruct { + google.protobuf.Struct struct = 1; +} diff --git a/integration/type-annotations/foo.ts b/integration/type-annotations/foo.ts new file mode 100644 index 000000000..0310c87a5 --- /dev/null +++ b/integration/type-annotations/foo.ts @@ -0,0 +1,236 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; +import { Struct } from "./google/protobuf/struct"; +import { Timestamp } from "./google/protobuf/timestamp"; + +export const protobufPackage = "foo"; + +export interface Foo { + $type: "foo.Foo"; + timestamp: Date | undefined; +} + +export interface Foo2 { + $type: "foo.Foo2"; + timestamp: Date | undefined; +} + +export interface WithStruct { + $type: "foo.WithStruct"; + struct: { [key: string]: any } | undefined; +} + +function createBaseFoo(): Foo { + return { $type: "foo.Foo", timestamp: undefined }; +} + +export const Foo = { + $type: "foo.Foo" as const, + + encode(message: Foo, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.timestamp !== undefined) { + Timestamp.encode(toTimestamp(message.timestamp), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Foo { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseFoo(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.timestamp = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Foo { + return { $type: Foo.$type, timestamp: isSet(object.timestamp) ? fromJsonTimestamp(object.timestamp) : undefined }; + }, + + toJSON(message: Foo): unknown { + const obj: any = {}; + message.timestamp !== undefined && (obj.timestamp = message.timestamp.toISOString()); + return obj; + }, + + create, I>>(base?: I): Foo { + return Foo.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Foo { + const message = createBaseFoo(); + message.timestamp = object.timestamp ?? undefined; + return message; + }, +}; + +function createBaseFoo2(): Foo2 { + return { $type: "foo.Foo2", timestamp: undefined }; +} + +export const Foo2 = { + $type: "foo.Foo2" as const, + + encode(message: Foo2, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.timestamp !== undefined) { + Timestamp.encode(toTimestamp(message.timestamp), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Foo2 { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseFoo2(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.timestamp = fromTimestamp(Timestamp.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Foo2 { + return { $type: Foo2.$type, timestamp: isSet(object.timestamp) ? fromJsonTimestamp(object.timestamp) : undefined }; + }, + + toJSON(message: Foo2): unknown { + const obj: any = {}; + message.timestamp !== undefined && (obj.timestamp = message.timestamp.toISOString()); + return obj; + }, + + create, I>>(base?: I): Foo2 { + return Foo2.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Foo2 { + const message = createBaseFoo2(); + message.timestamp = object.timestamp ?? undefined; + return message; + }, +}; + +function createBaseWithStruct(): WithStruct { + return { $type: "foo.WithStruct", struct: undefined }; +} + +export const WithStruct = { + $type: "foo.WithStruct" as const, + + encode(message: WithStruct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.struct !== undefined) { + Struct.encode(Struct.wrap(message.struct), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): WithStruct { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseWithStruct(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.struct = Struct.unwrap(Struct.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): WithStruct { + return { $type: WithStruct.$type, struct: isObject(object.struct) ? object.struct : undefined }; + }, + + toJSON(message: WithStruct): unknown { + const obj: any = {}; + message.struct !== undefined && (obj.struct = message.struct); + return obj; + }, + + create, I>>(base?: I): WithStruct { + return WithStruct.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): WithStruct { + const message = createBaseWithStruct(); + message.struct = object.struct ?? undefined; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in Exclude]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude | "$type">]: never }; + +function toTimestamp(date: Date): Timestamp { + const seconds = date.getTime() / 1_000; + const nanos = (date.getTime() % 1_000) * 1_000_000; + return { $type: "google.protobuf.Timestamp", seconds, nanos }; +} + +function fromTimestamp(t: Timestamp): Date { + let millis = t.seconds * 1_000; + millis += t.nanos / 1_000_000; + return new Date(millis); +} + +function fromJsonTimestamp(o: any): Date { + if (o instanceof Date) { + return o; + } else if (typeof o === "string") { + return new Date(o); + } else { + return fromTimestamp(Timestamp.fromJSON(o)); + } +} + +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/type-annotations/google/protobuf/struct.ts b/integration/type-annotations/google/protobuf/struct.ts new file mode 100644 index 000000000..4edb58b48 --- /dev/null +++ b/integration/type-annotations/google/protobuf/struct.ts @@ -0,0 +1,553 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; + +export const protobufPackage = "google.protobuf"; + +/** + * `NullValue` is a singleton enumeration to represent the null value for the + * `Value` type union. + * + * The JSON representation for `NullValue` is JSON `null`. + */ +export enum NullValue { + /** NULL_VALUE - Null value. */ + NULL_VALUE = 0, + UNRECOGNIZED = -1, +} + +export function nullValueFromJSON(object: any): NullValue { + switch (object) { + case 0: + case "NULL_VALUE": + return NullValue.NULL_VALUE; + case -1: + case "UNRECOGNIZED": + default: + return NullValue.UNRECOGNIZED; + } +} + +export function nullValueToJSON(object: NullValue): string { + switch (object) { + case NullValue.NULL_VALUE: + return "NULL_VALUE"; + case NullValue.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + +/** + * `Struct` represents a structured data value, consisting of fields + * which map to dynamically typed values. In some languages, `Struct` + * might be supported by a native representation. For example, in + * scripting languages like JS a struct is represented as an + * object. The details of that representation are described together + * with the proto support for the language. + * + * The JSON representation for `Struct` is JSON object. + */ +export interface Struct { + $type: "google.protobuf.Struct"; + /** Unordered map of dynamically typed values. */ + fields: { [key: string]: any | undefined }; +} + +export interface Struct_FieldsEntry { + $type: "google.protobuf.Struct.FieldsEntry"; + key: string; + value: any | undefined; +} + +/** + * `Value` represents a dynamically typed value which can be either + * null, a number, a string, a boolean, a recursive struct value, or a + * list of values. A producer of value is expected to set one of these + * variants. Absence of any variant indicates an error. + * + * The JSON representation for `Value` is JSON value. + */ +export interface Value { + $type: "google.protobuf.Value"; + /** Represents a null value. */ + nullValue?: + | NullValue + | undefined; + /** Represents a double value. */ + numberValue?: + | number + | undefined; + /** Represents a string value. */ + stringValue?: + | string + | undefined; + /** Represents a boolean value. */ + boolValue?: + | boolean + | undefined; + /** Represents a structured value. */ + structValue?: + | { [key: string]: any } + | undefined; + /** Represents a repeated `Value`. */ + listValue?: Array | undefined; +} + +/** + * `ListValue` is a wrapper around a repeated field of values. + * + * The JSON representation for `ListValue` is JSON array. + */ +export interface ListValue { + $type: "google.protobuf.ListValue"; + /** Repeated field of dynamically typed values. */ + values: any[]; +} + +function createBaseStruct(): Struct { + return { $type: "google.protobuf.Struct", fields: {} }; +} + +export const Struct = { + $type: "google.protobuf.Struct" as const, + + encode(message: Struct, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + Object.entries(message.fields).forEach(([key, value]) => { + if (value !== undefined) { + Struct_FieldsEntry.encode( + { $type: "google.protobuf.Struct.FieldsEntry", key: key as any, value }, + writer.uint32(10).fork(), + ).ldelim(); + } + }); + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Struct { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStruct(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + const entry1 = Struct_FieldsEntry.decode(reader, reader.uint32()); + if (entry1.value !== undefined) { + message.fields[entry1.key] = entry1.value; + } + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Struct { + return { + $type: Struct.$type, + fields: isObject(object.fields) + ? Object.entries(object.fields).reduce<{ [key: string]: any | undefined }>((acc, [key, value]) => { + acc[key] = value as any | undefined; + return acc; + }, {}) + : {}, + }; + }, + + toJSON(message: Struct): unknown { + const obj: any = {}; + obj.fields = {}; + if (message.fields) { + Object.entries(message.fields).forEach(([k, v]) => { + obj.fields[k] = v; + }); + } + return obj; + }, + + create, I>>(base?: I): Struct { + return Struct.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Struct { + const message = createBaseStruct(); + message.fields = Object.entries(object.fields ?? {}).reduce<{ [key: string]: any | undefined }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value; + } + return acc; + }, + {}, + ); + return message; + }, + + wrap(object: { [key: string]: any } | undefined): Struct { + const struct = createBaseStruct(); + if (object !== undefined) { + Object.keys(object).forEach((key) => { + struct.fields[key] = object[key]; + }); + } + return struct; + }, + + unwrap(message: Struct): { [key: string]: any } { + const object: { [key: string]: any } = {}; + if (message.fields) { + Object.keys(message.fields).forEach((key) => { + object[key] = message.fields[key]; + }); + } + return object; + }, +}; + +function createBaseStruct_FieldsEntry(): Struct_FieldsEntry { + return { $type: "google.protobuf.Struct.FieldsEntry", key: "", value: undefined }; +} + +export const Struct_FieldsEntry = { + $type: "google.protobuf.Struct.FieldsEntry" as const, + + encode(message: Struct_FieldsEntry, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + Value.encode(Value.wrap(message.value), writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Struct_FieldsEntry { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStruct_FieldsEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.key = reader.string(); + continue; + case 2: + if (tag !== 18) { + break; + } + + message.value = Value.unwrap(Value.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Struct_FieldsEntry { + return { + $type: Struct_FieldsEntry.$type, + key: isSet(object.key) ? String(object.key) : "", + value: isSet(object?.value) ? object.value : undefined, + }; + }, + + toJSON(message: Struct_FieldsEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + create, I>>(base?: I): Struct_FieldsEntry { + return Struct_FieldsEntry.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Struct_FieldsEntry { + const message = createBaseStruct_FieldsEntry(); + message.key = object.key ?? ""; + message.value = object.value ?? undefined; + return message; + }, +}; + +function createBaseValue(): Value { + return { + $type: "google.protobuf.Value", + nullValue: undefined, + numberValue: undefined, + stringValue: undefined, + boolValue: undefined, + structValue: undefined, + listValue: undefined, + }; +} + +export const Value = { + $type: "google.protobuf.Value" as const, + + encode(message: Value, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.nullValue !== undefined) { + writer.uint32(8).int32(message.nullValue); + } + if (message.numberValue !== undefined) { + writer.uint32(17).double(message.numberValue); + } + if (message.stringValue !== undefined) { + writer.uint32(26).string(message.stringValue); + } + if (message.boolValue !== undefined) { + writer.uint32(32).bool(message.boolValue); + } + if (message.structValue !== undefined) { + Struct.encode(Struct.wrap(message.structValue), writer.uint32(42).fork()).ldelim(); + } + if (message.listValue !== undefined) { + ListValue.encode(ListValue.wrap(message.listValue), writer.uint32(50).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Value { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseValue(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.nullValue = reader.int32() as any; + continue; + case 2: + if (tag !== 17) { + break; + } + + message.numberValue = reader.double(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.stringValue = reader.string(); + continue; + case 4: + if (tag !== 32) { + break; + } + + message.boolValue = reader.bool(); + continue; + case 5: + if (tag !== 42) { + break; + } + + message.structValue = Struct.unwrap(Struct.decode(reader, reader.uint32())); + continue; + case 6: + if (tag !== 50) { + break; + } + + message.listValue = ListValue.unwrap(ListValue.decode(reader, reader.uint32())); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Value { + return { + $type: Value.$type, + nullValue: isSet(object.nullValue) ? nullValueFromJSON(object.nullValue) : undefined, + numberValue: isSet(object.numberValue) ? Number(object.numberValue) : undefined, + stringValue: isSet(object.stringValue) ? String(object.stringValue) : undefined, + boolValue: isSet(object.boolValue) ? Boolean(object.boolValue) : undefined, + structValue: isObject(object.structValue) ? object.structValue : undefined, + listValue: Array.isArray(object.listValue) ? [...object.listValue] : undefined, + }; + }, + + toJSON(message: Value): unknown { + const obj: any = {}; + message.nullValue !== undefined && + (obj.nullValue = message.nullValue !== undefined ? nullValueToJSON(message.nullValue) : undefined); + message.numberValue !== undefined && (obj.numberValue = message.numberValue); + message.stringValue !== undefined && (obj.stringValue = message.stringValue); + message.boolValue !== undefined && (obj.boolValue = message.boolValue); + message.structValue !== undefined && (obj.structValue = message.structValue); + message.listValue !== undefined && (obj.listValue = message.listValue); + return obj; + }, + + create, I>>(base?: I): Value { + return Value.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Value { + const message = createBaseValue(); + message.nullValue = object.nullValue ?? undefined; + message.numberValue = object.numberValue ?? undefined; + message.stringValue = object.stringValue ?? undefined; + message.boolValue = object.boolValue ?? undefined; + message.structValue = object.structValue ?? undefined; + message.listValue = object.listValue ?? undefined; + return message; + }, + + wrap(value: any): Value { + const result = createBaseValue(); + if (value === null) { + result.nullValue = NullValue.NULL_VALUE; + } else if (typeof value === "boolean") { + result.boolValue = value; + } else if (typeof value === "number") { + result.numberValue = value; + } else if (typeof value === "string") { + result.stringValue = value; + } else if (Array.isArray(value)) { + result.listValue = value; + } else if (typeof value === "object") { + result.structValue = value; + } else if (typeof value !== "undefined") { + throw new Error("Unsupported any value type: " + typeof value); + } + return result; + }, + + unwrap(message: any): string | number | boolean | Object | null | Array | undefined { + if (message.stringValue !== undefined) { + return message.stringValue; + } else if (message?.numberValue !== undefined) { + return message.numberValue; + } else if (message?.boolValue !== undefined) { + return message.boolValue; + } else if (message?.structValue !== undefined) { + return message.structValue as any; + } else if (message?.listValue !== undefined) { + return message.listValue; + } else if (message?.nullValue !== undefined) { + return null; + } + return undefined; + }, +}; + +function createBaseListValue(): ListValue { + return { $type: "google.protobuf.ListValue", values: [] }; +} + +export const ListValue = { + $type: "google.protobuf.ListValue" as const, + + encode(message: ListValue, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + for (const v of message.values) { + Value.encode(Value.wrap(v!), writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ListValue { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseListValue(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.values.push(Value.unwrap(Value.decode(reader, reader.uint32()))); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): ListValue { + return { $type: ListValue.$type, values: Array.isArray(object?.values) ? [...object.values] : [] }; + }, + + toJSON(message: ListValue): unknown { + const obj: any = {}; + if (message.values) { + obj.values = message.values.map((e) => e); + } else { + obj.values = []; + } + return obj; + }, + + create, I>>(base?: I): ListValue { + return ListValue.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): ListValue { + const message = createBaseListValue(); + message.values = object.values?.map((e) => e) || []; + return message; + }, + + wrap(array: Array | undefined): ListValue { + const result = createBaseListValue(); + result.values = array ?? []; + return result; + }, + + unwrap(message: ListValue): Array { + if (message?.hasOwnProperty("values") && Array.isArray(message.values)) { + return message.values; + } else { + return message as any; + } + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in Exclude]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude | "$type">]: never }; + +function isObject(value: any): boolean { + return typeof value === "object" && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/type-annotations/google/protobuf/timestamp.ts b/integration/type-annotations/google/protobuf/timestamp.ts new file mode 100644 index 000000000..ebe4a8b4e --- /dev/null +++ b/integration/type-annotations/google/protobuf/timestamp.ts @@ -0,0 +1,235 @@ +/* eslint-disable */ +import * as Long from "long"; +import * as _m0 from "protobufjs/minimal"; + +export const protobufPackage = "google.protobuf"; + +/** + * A Timestamp represents a point in time independent of any time zone or local + * calendar, encoded as a count of seconds and fractions of seconds at + * nanosecond resolution. The count is relative to an epoch at UTC midnight on + * January 1, 1970, in the proleptic Gregorian calendar which extends the + * Gregorian calendar backwards to year one. + * + * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap + * second table is needed for interpretation, using a [24-hour linear + * smear](https://developers.google.com/time/smear). + * + * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By + * restricting to that range, we ensure that we can convert to and from [RFC + * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. + * + * # Examples + * + * Example 1: Compute Timestamp from POSIX `time()`. + * + * Timestamp timestamp; + * timestamp.set_seconds(time(NULL)); + * timestamp.set_nanos(0); + * + * Example 2: Compute Timestamp from POSIX `gettimeofday()`. + * + * struct timeval tv; + * gettimeofday(&tv, NULL); + * + * Timestamp timestamp; + * timestamp.set_seconds(tv.tv_sec); + * timestamp.set_nanos(tv.tv_usec * 1000); + * + * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. + * + * FILETIME ft; + * GetSystemTimeAsFileTime(&ft); + * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + * + * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z + * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. + * Timestamp timestamp; + * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); + * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); + * + * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. + * + * long millis = System.currentTimeMillis(); + * + * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) + * .setNanos((int) ((millis % 1000) * 1000000)).build(); + * + * Example 5: Compute Timestamp from Java `Instant.now()`. + * + * Instant now = Instant.now(); + * + * Timestamp timestamp = + * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) + * .setNanos(now.getNano()).build(); + * + * Example 6: Compute Timestamp from current time in Python. + * + * timestamp = Timestamp() + * timestamp.GetCurrentTime() + * + * # JSON Mapping + * + * In JSON format, the Timestamp type is encoded as a string in the + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the + * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" + * where {year} is always expressed using four digits while {month}, {day}, + * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional + * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), + * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone + * is required. A proto3 JSON serializer should always use UTC (as indicated by + * "Z") when printing the Timestamp type and a proto3 JSON parser should be + * able to accept both UTC and other timezones (as indicated by an offset). + * + * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past + * 01:30 UTC on January 15, 2017. + * + * In JavaScript, one can convert a Date object to this format using the + * standard + * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) + * method. In Python, a standard `datetime.datetime` object can be converted + * to this format using + * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with + * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use + * the Joda Time's [`ISODateTimeFormat.dateTime()`]( + * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D + * ) to obtain a formatter capable of generating timestamps in this format. + */ +export interface Timestamp { + $type: "google.protobuf.Timestamp"; + /** + * Represents seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + */ + seconds: number; + /** + * Non-negative fractions of a second at nanosecond resolution. Negative + * second values with fractions must still have non-negative nanos values + * that count forward in time. Must be from 0 to 999,999,999 + * inclusive. + */ + nanos: number; +} + +function createBaseTimestamp(): Timestamp { + return { $type: "google.protobuf.Timestamp", seconds: 0, nanos: 0 }; +} + +export const Timestamp = { + $type: "google.protobuf.Timestamp" as const, + + encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.seconds !== 0) { + writer.uint32(8).int64(message.seconds); + } + if (message.nanos !== 0) { + writer.uint32(16).int32(message.nanos); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTimestamp(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 8) { + break; + } + + message.seconds = longToNumber(reader.int64() as Long); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.nanos = reader.int32(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Timestamp { + return { + $type: Timestamp.$type, + seconds: isSet(object.seconds) ? Number(object.seconds) : 0, + nanos: isSet(object.nanos) ? Number(object.nanos) : 0, + }; + }, + + toJSON(message: Timestamp): unknown { + const obj: any = {}; + message.seconds !== undefined && (obj.seconds = Math.round(message.seconds)); + message.nanos !== undefined && (obj.nanos = Math.round(message.nanos)); + return obj; + }, + + create, I>>(base?: I): Timestamp { + return Timestamp.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): Timestamp { + const message = createBaseTimestamp(); + message.seconds = object.seconds ?? 0; + message.nanos = object.nanos ?? 0; + return message; + }, +}; + +declare var self: any | undefined; +declare var window: any | undefined; +declare var global: any | undefined; +var tsProtoGlobalThis: any = (() => { + if (typeof globalThis !== "undefined") { + return globalThis; + } + if (typeof self !== "undefined") { + return self; + } + if (typeof window !== "undefined") { + return window; + } + if (typeof global !== "undefined") { + return global; + } + throw "Unable to locate global object"; +})(); + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in Exclude]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude | "$type">]: never }; + +function longToNumber(long: Long): number { + if (long.gt(Number.MAX_SAFE_INTEGER)) { + throw new tsProtoGlobalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + return long.toNumber(); +} + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/type-annotations/parameters.txt b/integration/type-annotations/parameters.txt new file mode 100755 index 000000000..d48fe9630 --- /dev/null +++ b/integration/type-annotations/parameters.txt @@ -0,0 +1 @@ +outputTypeAnnotations=true diff --git a/integration/type-annotations/type-registry-test.ts b/integration/type-annotations/type-registry-test.ts new file mode 100644 index 000000000..0c9630c29 --- /dev/null +++ b/integration/type-annotations/type-registry-test.ts @@ -0,0 +1,35 @@ +import { Foo, Foo2 } from './foo'; +import { Bar } from './bar/bar'; + +describe('type-annotations', () => { + it('should output $type field for every message', () => { + expect(Foo.fromPartial({})).toMatchInlineSnapshot(` + Object { + "$type": "foo.Foo", + "timestamp": undefined, + } + `); + expect(Bar.fromPartial({})).toMatchInlineSnapshot(` + Object { + "$type": "foo.bar.Bar", + "foo": undefined, + } + `); + }); + + it('should ignore $type field when creating from partial', () => { + const foo2 = Foo2.fromPartial({}); + expect(foo2).toMatchInlineSnapshot(` + Object { + "$type": "foo.Foo2", + "timestamp": undefined, + } + `); + expect(Foo.fromPartial(foo2)).toMatchInlineSnapshot(` + Object { + "$type": "foo.Foo", + "timestamp": undefined, + } + `); + }); +}); diff --git a/src/main.ts b/src/main.ts index 0496ae0bf..0ff29936e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -172,7 +172,13 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri // We add `nestJs` here because enough though it doesn't use our encode/decode methods // for most/vanilla messages, we do generate static wrap/unwrap methods for the special // Struct/Value/wrapper types and use the `wrappers[...]` to have NestJS know about them. - if (options.outputEncodeMethods || options.outputJsonMethods || options.outputTypeRegistry || options.nestJs) { + if ( + options.outputEncodeMethods || + options.outputJsonMethods || + options.outputTypeAnnotations || + options.outputTypeRegistry || + options.nestJs + ) { // then add the encoder/decoder/base instance visit( fileDesc, @@ -192,7 +198,7 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri const staticMembers: Code[] = []; - if (options.outputTypeRegistry) { + if (options.outputTypeAnnotations || options.outputTypeRegistry) { staticMembers.push(code`$type: '${fullTypeName}' as const`); } @@ -596,7 +602,7 @@ function makeDeepPartial(options: Options, longs: ReturnType` : code`keyof T`; + const keys = + options.outputTypeAnnotations || options.outputTypeRegistry ? code`Exclude` : code`keyof T`; const DeepPartial = conditionalOutput( "DeepPartial", code` @@ -688,7 +695,8 @@ function makeTimestampMethods(options: Options, longs: ReturnType code`${type}.encode(${utils.toTimestamp}(${place}), writer.uint32(${tag}).fork()).ldelim()`; } else if (isValueType(ctx, field)) { - const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ""; + const maybeTypeField = + options.outputTypeAnnotations || options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ""; const type = basicTypeName(ctx, field, { keepValueType: true }); const wrappedValue = (place: string): Code => { @@ -1299,7 +1308,8 @@ function generateEncode(ctx: Context, fullName: string, messageDesc: DescriptorP if (isRepeated(field)) { if (isMapType(ctx, messageDesc, field)) { const valueType = (typeMap.get(field.typeName)![2] as DescriptorProto).field[1]; - const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ""; + const maybeTypeField = + options.outputTypeAnnotations || options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ""; const entryWriteSnippet = isValueType(ctx, valueType) ? code` if (value !== undefined) { @@ -1550,7 +1560,8 @@ function generateExtension(ctx: Context, message: DescriptorProto | undefined, e const type = basicTypeName(ctx, field, { keepValueType: true }); return (place) => code`${type}.encode(${utils.toTimestamp}(${place}), writer.fork()).ldelim()`; } else if (isValueType(ctx, field)) { - const maybeTypeField = options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ""; + const maybeTypeField = + options.outputTypeAnnotations || options.outputTypeRegistry ? `$type: '${field.typeName.slice(1)}',` : ""; const type = basicTypeName(ctx, field, { keepValueType: true }); const wrappedValue = (place: string): Code => { @@ -1695,7 +1706,7 @@ function generateFromJson(ctx: Context, fullName: string, fullTypeName: string, return { `); - if (ctx.options.outputTypeRegistry) { + if (ctx.options.outputTypeAnnotations || ctx.options.outputTypeRegistry) { chunks.push(code`$type: ${fullName}.$type,`); } diff --git a/src/options.ts b/src/options.ts index 16f562f44..513d27095 100644 --- a/src/options.ts +++ b/src/options.ts @@ -48,6 +48,7 @@ export type Options = { outputEncodeMethods: true | false | "encode-only" | "decode-only" | "encode-no-creation"; outputJsonMethods: boolean; outputPartialMethods: boolean; + outputTypeAnnotations: boolean; outputTypeRegistry: boolean; stringEnums: boolean; constEnums: boolean; @@ -98,6 +99,7 @@ export function defaultOptions(): Options { outputEncodeMethods: true, outputJsonMethods: true, outputPartialMethods: true, + outputTypeAnnotations: false, outputTypeRegistry: false, stringEnums: false, constEnums: false, diff --git a/tests/options-test.ts b/tests/options-test.ts index 367e0a45f..f9c4f655f 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -32,6 +32,7 @@ describe("options", () => { "outputServices": Array [ "default", ], + "outputTypeAnnotations": false, "outputTypeRegistry": false, "removeEnumPrefix": false, "returnObservable": false,