diff --git a/README.markdown b/README.markdown index f3327f1ef..a6834d21a 100644 --- a/README.markdown +++ b/README.markdown @@ -539,7 +539,9 @@ Generated code will be placed in the Gradle build directory. This will disable `exportCommonSymbols` to avoid name collisions on the common symbols. -- With `--emitDefaultValues=json-methods`, the generated toJSON method will emit scalars like `0` and `""` as json fields. +- With `--ts_proto_opt=emitDefaultValues=json-methods`, the generated toJSON method will emit scalars like `0` and `""` as json fields. + +- With `--ts_proto_opt=comments=false`, comments won't be copied from the proto files to the generated code. ### NestJS Support diff --git a/integration/no-comments/parameters.txt b/integration/no-comments/parameters.txt new file mode 100644 index 000000000..7ce64a2cb --- /dev/null +++ b/integration/no-comments/parameters.txt @@ -0,0 +1 @@ +comments=false diff --git a/integration/no-comments/simple.bin b/integration/no-comments/simple.bin new file mode 100644 index 000000000..02c48b052 Binary files /dev/null and b/integration/no-comments/simple.bin differ diff --git a/integration/no-comments/simple.proto b/integration/no-comments/simple.proto new file mode 100644 index 000000000..3d8dbdf88 --- /dev/null +++ b/integration/no-comments/simple.proto @@ -0,0 +1,23 @@ +// Adding a comment to the syntax will become the first +// comment in the output source file. +syntax = "proto3"; + +package simple; + +// This comment is separated by a blank non-comment line, and will detach from +// the following comment on the message Simple. + +/** Example comment on the Simple message */ +message Simple { + // Name field + string name = 1 [deprecated = true]; + /** Age field */ + int32 age = 2 [deprecated = true]; + Child child = 3 [deprecated = true]; // This comment will also attach; + string test_field = 4 [deprecated = true]; + string test_not_deprecated = 5 [deprecated = false]; +} + +message Child { + string name = 1; +} diff --git a/integration/no-comments/simple.ts b/integration/no-comments/simple.ts new file mode 100644 index 000000000..a5da0481b --- /dev/null +++ b/integration/no-comments/simple.ts @@ -0,0 +1,208 @@ +/* eslint-disable */ +import * as _m0 from "protobufjs/minimal"; + +export const protobufPackage = "simple"; + +export interface Simple { + name: string; + age: number; + child: Child | undefined; + testField: string; + testNotDeprecated: string; +} + +export interface Child { + name: string; +} + +function createBaseSimple(): Simple { + return { name: "", age: 0, child: undefined, testField: "", testNotDeprecated: "" }; +} + +export const Simple = { + encode(message: Simple, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + if (message.age !== 0) { + writer.uint32(16).int32(message.age); + } + if (message.child !== undefined) { + Child.encode(message.child, writer.uint32(26).fork()).ldelim(); + } + if (message.testField !== "") { + writer.uint32(34).string(message.testField); + } + if (message.testNotDeprecated !== "") { + writer.uint32(42).string(message.testNotDeprecated); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Simple { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSimple(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + case 2: + if (tag !== 16) { + break; + } + + message.age = reader.int32(); + continue; + case 3: + if (tag !== 26) { + break; + } + + message.child = Child.decode(reader, reader.uint32()); + continue; + case 4: + if (tag !== 34) { + break; + } + + message.testField = reader.string(); + continue; + case 5: + if (tag !== 42) { + break; + } + + message.testNotDeprecated = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Simple { + return { + name: isSet(object.name) ? globalThis.String(object.name) : "", + age: isSet(object.age) ? globalThis.Number(object.age) : 0, + child: isSet(object.child) ? Child.fromJSON(object.child) : undefined, + testField: isSet(object.testField) ? globalThis.String(object.testField) : "", + testNotDeprecated: isSet(object.testNotDeprecated) ? globalThis.String(object.testNotDeprecated) : "", + }; + }, + + toJSON(message: Simple): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + if (message.age !== 0) { + obj.age = Math.round(message.age); + } + if (message.child !== undefined) { + obj.child = Child.toJSON(message.child); + } + if (message.testField !== "") { + obj.testField = message.testField; + } + if (message.testNotDeprecated !== "") { + obj.testNotDeprecated = message.testNotDeprecated; + } + return obj; + }, + + create, I>>(base?: I): Simple { + return Simple.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Simple { + const message = createBaseSimple(); + message.name = object.name ?? ""; + message.age = object.age ?? 0; + message.child = (object.child !== undefined && object.child !== null) ? Child.fromPartial(object.child) : undefined; + message.testField = object.testField ?? ""; + message.testNotDeprecated = object.testNotDeprecated ?? ""; + return message; + }, +}; + +function createBaseChild(): Child { + return { name: "" }; +} + +export const Child = { + encode(message: Child, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Child { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseChild(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): Child { + return { name: isSet(object.name) ? globalThis.String(object.name) : "" }; + }, + + toJSON(message: Child): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + return obj; + }, + + create, I>>(base?: I): Child { + return Child.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Child { + const message = createBaseChild(); + message.name = object.name ?? ""; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: 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>]: never }; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/src/enums.ts b/src/enums.ts index 3e9721f43..0ed443da5 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -18,7 +18,7 @@ export function generateEnum( const chunks: Code[] = []; let unrecognizedEnum: UnrecognizedEnum = { present: false }; - maybeAddComment(sourceInfo, chunks, enumDesc.options?.deprecated); + maybeAddComment(options, sourceInfo, chunks, enumDesc.options?.deprecated); if (options.enumsAsLiterals) { chunks.push(code`export const ${def(fullName)} = {`); @@ -35,7 +35,7 @@ export function generateEnum( if (valueDesc.number === options.unrecognizedEnumValue) { unrecognizedEnum = { present: true, name: memberName }; } - maybeAddComment(info, chunks, valueDesc.options?.deprecated, `${memberName} - `); + maybeAddComment(options, info, chunks, valueDesc.options?.deprecated, `${memberName} - `); chunks.push( code`${memberName} ${delimiter} ${options.stringEnums ? `"${valueName}"` : valueDesc.number.toString()},`, ); diff --git a/src/generate-generic-service-definition.ts b/src/generate-generic-service-definition.ts index a05325a51..c316bcdb6 100644 --- a/src/generate-generic-service-definition.ts +++ b/src/generate-generic-service-definition.ts @@ -23,7 +23,7 @@ export function generateGenericServiceDefinition( ) { const chunks: Code[] = []; - maybeAddComment(sourceInfo, chunks, serviceDesc.options?.deprecated); + maybeAddComment(ctx.options, sourceInfo, chunks, serviceDesc.options?.deprecated); // Service definition type const name = def(`${serviceDesc.name}Definition`); @@ -45,7 +45,7 @@ export function generateGenericServiceDefinition( for (const [index, methodDesc] of serviceDesc.method.entries()) { const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(ctx.options, info, chunks, methodDesc.options?.deprecated); chunks.push(code` ${uncapitalize(methodDesc.name)}: ${generateMethodDefinition(ctx, methodDesc)}, diff --git a/src/generate-grpc-js.ts b/src/generate-grpc-js.ts index ea6e3c8fa..40a50e8eb 100644 --- a/src/generate-grpc-js.ts +++ b/src/generate-grpc-js.ts @@ -54,7 +54,7 @@ function generateServiceDefinition( ) { const chunks: Code[] = []; - maybeAddComment(sourceInfo, chunks, serviceDesc.options?.deprecated); + maybeAddComment(ctx.options, sourceInfo, chunks, serviceDesc.options?.deprecated); // Service definition type const name = def(`${serviceDesc.name}Service`); @@ -74,7 +74,7 @@ function generateServiceDefinition( const outputType = messageToTypeName(ctx, methodDesc.outputType); const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(ctx.options, info, chunks, methodDesc.options?.deprecated); const inputEncoder = generateEncoder(ctx, methodDesc.inputType); const outputEncoder = generateEncoder(ctx, methodDesc.outputType); @@ -114,7 +114,7 @@ function generateServerStub(ctx: Context, sourceInfo: SourceInfo, serviceDesc: S const outputType = messageToTypeName(ctx, methodDesc.outputType); const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(ctx.options, info, chunks, methodDesc.options?.deprecated); const callType = methodDesc.clientStreaming ? methodDesc.serverStreaming @@ -146,7 +146,7 @@ function generateClientStub(ctx: Context, sourceInfo: SourceInfo, serviceDesc: S const outputType = messageToTypeName(ctx, methodDesc.outputType); const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(ctx.options, info, chunks, methodDesc.options?.deprecated); const responseCallback = code`(error: ${ServiceError} | null, response: ${outputType}) => void`; diff --git a/src/generate-nestjs.ts b/src/generate-nestjs.ts index 59b16aca7..78bed98bd 100644 --- a/src/generate-nestjs.ts +++ b/src/generate-nestjs.ts @@ -26,7 +26,7 @@ export function generateNestjsServiceController( const Metadata = imp("Metadata@@grpc/grpc-js"); - maybeAddComment(sourceInfo, chunks, serviceDesc.options?.deprecated); + maybeAddComment(options, sourceInfo, chunks, serviceDesc.options?.deprecated); const t = options.context ? `<${contextTypeVar}>` : ""; chunks.push(code` export interface ${serviceDesc.name}Controller${t} { @@ -35,7 +35,7 @@ export function generateNestjsServiceController( serviceDesc.method.forEach((methodDesc, index) => { assertInstanceOf(methodDesc, FormattedMethodDescriptor); const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, serviceDesc.options?.deprecated); + maybeAddComment(options, info, chunks, serviceDesc.options?.deprecated); const params: Code[] = []; if (options.context) { @@ -99,7 +99,7 @@ export function generateNestjsServiceClient( const Metadata = imp("Metadata@@grpc/grpc-js"); - maybeAddComment(sourceInfo, chunks); + maybeAddComment(options, sourceInfo, chunks); const t = options.context ? `<${contextTypeVar}>` : ``; chunks.push(code` export interface ${serviceDesc.name}Client${t} { @@ -125,7 +125,7 @@ export function generateNestjsServiceClient( const returns = responseObservable(ctx, methodDesc); const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(options, info, chunks, methodDesc.options?.deprecated); chunks.push(code` ${methodDesc.formattedName}( ${joinCode(params, { on: "," })} diff --git a/src/generate-nice-grpc.ts b/src/generate-nice-grpc.ts index 0a6ac451c..301f62782 100644 --- a/src/generate-nice-grpc.ts +++ b/src/generate-nice-grpc.ts @@ -44,7 +44,7 @@ function generateServerStub(ctx: Context, sourceInfo: SourceInfo, serviceDesc: S const ServerStreamingMethodResult = ctx.utils.NiceGrpcServerStreamingMethodResult; const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(ctx.options, info, chunks, methodDesc.options?.deprecated); if (methodDesc.clientStreaming) { if (methodDesc.serverStreaming) { @@ -107,7 +107,7 @@ function generateClientStub(ctx: Context, sourceInfo: SourceInfo, serviceDesc: S const outputType = messageToTypeName(ctx, methodDesc.outputType, { keepValueType: true }); const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(ctx.options, info, chunks, methodDesc.options?.deprecated); if (methodDesc.clientStreaming) { if (methodDesc.serverStreaming) { diff --git a/src/generate-services.ts b/src/generate-services.ts index 2a1d9f33d..0c59232d2 100644 --- a/src/generate-services.ts +++ b/src/generate-services.ts @@ -41,14 +41,14 @@ export function generateService( const { options } = ctx; const chunks: Code[] = []; - maybeAddComment(sourceInfo, chunks, serviceDesc.options?.deprecated); + maybeAddComment(options, sourceInfo, chunks, serviceDesc.options?.deprecated); const maybeTypeVar = options.context ? `<${contextTypeVar}>` : ""; chunks.push(code`export interface ${def(serviceDesc.name)}${maybeTypeVar} {`); serviceDesc.method.forEach((methodDesc, index) => { assertInstanceOf(methodDesc, FormattedMethodDescriptor); const info = sourceInfo.lookup(Fields.service.method, index); - maybeAddComment(info, chunks, methodDesc.options?.deprecated); + maybeAddComment(options, info, chunks, methodDesc.options?.deprecated); const params: Code[] = []; if (options.context) { diff --git a/src/main.ts b/src/main.ts index c771130aa..e02f06e53 100644 --- a/src/main.ts +++ b/src/main.ts @@ -139,7 +139,7 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri // Syntax, unlike most fields, is not repeated and thus does not use an index const sourceInfo = SourceInfo.fromDescriptor(fileDesc); const headerComment = sourceInfo.lookup(Fields.file.syntax, undefined); - maybeAddComment(headerComment, chunks, fileDesc.options?.deprecated); + maybeAddComment(options, headerComment, chunks, fileDesc.options?.deprecated); // Apply formatting to methods here, so they propagate globally for (let svc of fileDesc.service) { @@ -893,7 +893,7 @@ function generateInterfaceDeclaration( const { options } = ctx; const chunks: Code[] = []; - maybeAddComment(sourceInfo, chunks, messageDesc.options?.deprecated); + maybeAddComment(options, sourceInfo, chunks, messageDesc.options?.deprecated); // interface name should be defined to avoid import collisions chunks.push(code`export interface ${def(fullName)} {`); @@ -915,7 +915,7 @@ function generateInterfaceDeclaration( } const info = sourceInfo.lookup(Fields.message.field, index); - maybeAddComment(info, chunks, fieldDesc.options?.deprecated); + maybeAddComment(options, info, chunks, fieldDesc.options?.deprecated); const fieldKey = safeAccessor(getFieldName(fieldDesc, options)); const isOptional = isOptionalProperty(fieldDesc, messageDesc.options, options); const type = toTypeName(ctx, messageDesc, fieldDesc, isOptional); @@ -957,14 +957,14 @@ function generateOneofProperty( // that ability. For now just concatenate all comments into one big one. let comments: Array = []; const info = sourceInfo.lookup(Fields.message.oneof_decl, oneofIndex); - maybeAddComment(info, (text) => comments.push(text)); + maybeAddComment(options, info, (text) => comments.push(text)); messageDesc.field.forEach((field, index) => { if (!isWithinOneOf(field) || field.oneofIndex !== oneofIndex) { return; } const info = sourceInfo.lookup(Fields.message.field, index); const name = maybeSnakeToCamel(field.name, options); - maybeAddComment(info, (text) => comments.push(name + '\n' + text)); + maybeAddComment(options, info, (text) => comments.push(name + '\n' + text)); }); if (comments.length) { prop = prop.addJavadoc(comments.join('\n')); @@ -1492,7 +1492,7 @@ function generateEncode(ctx: Context, fullName: string, messageDesc: DescriptorP throw new ${ utils.globalThis }.Error('a value provided in array field ${fieldName} of type ${fieldType} is too large'); - } + } writer.${toReaderCall(field)}(${rhs("v")}); } writer.ldelim(); @@ -2249,13 +2249,13 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri chunks.push(code` create, I>>(base?: I): ${fullName} { return ${fullName}.fromPartial(base ?? ({} as any)); - }, + }, `); } else { chunks.push(code` create(base?: ${utils.DeepPartial}<${fullName}>): ${fullName} { return ${fullName}.fromPartial(base ?? {}); - }, + }, `); } diff --git a/src/options.ts b/src/options.ts index 89cfe5419..11d849d3c 100644 --- a/src/options.ts +++ b/src/options.ts @@ -95,6 +95,7 @@ export type Options = { rpcBeforeRequest: boolean; rpcAfterResponse: boolean; rpcErrorHandler: boolean; + comments: boolean; }; export function defaultOptions(): Options { @@ -155,6 +156,7 @@ export function defaultOptions(): Options { rpcBeforeRequest: false, rpcAfterResponse: false, rpcErrorHandler: false, + comments: true, }; } diff --git a/src/utils.ts b/src/utils.ts index b17abb608..b36c89e86 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -96,11 +96,16 @@ const CloseComment = /\*\//g; /** Removes potentially harmful characters from comments and pushes it into chunks. */ export function maybeAddComment( + options: Pick, desc: Partial>, chunks: Code[], deprecated?: boolean, prefix: string = "", ): void { + if (!options.comments) { + return; + } + let lines: string[] = []; if (desc.leadingComments || desc.trailingComments) { let content = (desc.leadingComments || desc.trailingComments || "").replace(CloseComment, "* /").trim(); diff --git a/tests/options-test.ts b/tests/options-test.ts index 6cb18944b..41761b375 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -7,6 +7,7 @@ describe("options", () => { "M": {}, "addGrpcMetadata": false, "addNestjsRestParameter": false, + "comments": true, "constEnums": false, "context": false, "emitDefaultValues": [], diff --git a/tests/utils-test.ts b/tests/utils-test.ts index 7de14ef8a..633a4b228 100644 --- a/tests/utils-test.ts +++ b/tests/utils-test.ts @@ -8,7 +8,7 @@ describe("utils", () => { it("handles single-line impl comments", () => { // Foo const chunks: Code[] = []; - maybeAddComment({ leadingComments: " Foo\n" }, chunks); + maybeAddComment({ comments: true }, { leadingComments: " Foo\n" }, chunks); expect(joinCode(chunks).toString()).toMatchInlineSnapshot(` "/** Foo */ " @@ -18,7 +18,7 @@ describe("utils", () => { it("handles single-dot star comments", () => { // /* Foo */ const chunks: Code[] = []; - maybeAddComment({ leadingComments: " Foo " }, chunks); + maybeAddComment({ comments: true }, { leadingComments: " Foo " }, chunks); expect(joinCode(chunks).toString()).toMatchInlineSnapshot(` "/** Foo */ " @@ -28,7 +28,7 @@ describe("utils", () => { it("handles single-line double-dot star comments", () => { // /** Foo */ const chunks: Code[] = []; - maybeAddComment({ leadingComments: " * Foo " }, chunks); + maybeAddComment({ comments: true }, { leadingComments: " * Foo " }, chunks); expect(joinCode(chunks).toString()).toMatchInlineSnapshot(` "/** Foo */ " @@ -42,7 +42,7 @@ describe("utils", () => { // * bar. // */ const chunks: Code[] = []; - maybeAddComment({ leadingComments: "*\n Foo\n \n bar.\n" }, chunks); + maybeAddComment({ comments: true }, { leadingComments: "*\n Foo\n \n bar.\n" }, chunks); expect(joinCode(chunks).toString()).toMatchInlineSnapshot(` "/** * Foo @@ -57,7 +57,7 @@ describe("utils", () => { // // Foo // // Bar const chunks: Code[] = []; - maybeAddComment({ leadingComments: " Foo\n Bar\n" }, chunks); + maybeAddComment({ comments: true }, { leadingComments: " Foo\n Bar\n" }, chunks); expect(joinCode(chunks).toString()).toMatchInlineSnapshot(` "/** * Foo @@ -66,6 +66,12 @@ describe("utils", () => { " `); }); + + it("doesn't emit comments when disabled", () => { + const chunks: Code[] = []; + maybeAddComment({ comments: false }, { leadingComments: " Foo\n" }, chunks); + expect(joinCode(chunks).toString()).toMatchInlineSnapshot(`""`); + }); }); describe("generateIndexFiles", () => {