Skip to content

Commit

Permalink
Add GeneratedFile.jsDoc() and other features to @bufbuild/protoplugin (
Browse files Browse the repository at this point in the history
  • Loading branch information
timostamm committed Dec 8, 2023
1 parent 8f13a7d commit 360bdb9
Show file tree
Hide file tree
Showing 22 changed files with 641 additions and 175 deletions.
16 changes: 7 additions & 9 deletions packages/protoc-gen-es/src/declaration.ts
Expand Up @@ -25,9 +25,7 @@ import type {
} from "@bufbuild/protoplugin/ecmascript";
import {
getFieldTyping,
literalString,
localName,
makeJsDoc,
reifyWkt,
} from "@bufbuild/protoplugin/ecmascript";
import { getNonEditionRuntime } from "./editions.js";
Expand All @@ -48,13 +46,13 @@ export function generateDts(schema: Schema) {

// prettier-ignore
function generateEnum(schema: Schema, f: GeneratedFile, enumeration: DescEnum) {
f.print(makeJsDoc(enumeration));
f.print(f.jsDoc(enumeration));
f.print("export declare enum ", enumeration, " {");
for (const value of enumeration.values) {
if (enumeration.values.indexOf(value) > 0) {
f.print();
}
f.print(makeJsDoc(value, " "));
f.print(f.jsDoc(value, " "));
f.print(" ", localName(value), " = ", value.number, ",");
}
f.print("}");
Expand All @@ -73,7 +71,7 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
JsonReadOptions,
JsonValue
} = schema.runtime;
f.print(makeJsDoc(message));
f.print(f.jsDoc(message));
f.print("export declare class ", message, " extends ", Message, "<", message, "> {");
for (const member of message.members) {
switch (member.kind) {
Expand All @@ -90,7 +88,7 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
f.print();
generateWktMethods(schema, f, message);
f.print(" static readonly runtime: typeof ", protoN, ";");
f.print(' static readonly typeName = ', literalString(message.typeName), ';');
f.print(' static readonly typeName = ', f.string(message.typeName), ';');
f.print(" static readonly fields: ", FieldList, ";");
// In case we start supporting options, we have to surface them here
//f.print(" static readonly options: { readonly [extensionName: string]: ", rt.JsonValue, " } = {};")
Expand All @@ -116,13 +114,13 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)

// prettier-ignore
function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
f.print(makeJsDoc(oneof, " "));
f.print(f.jsDoc(oneof, " "));
f.print(" ", localName(oneof), ": {");
for (const field of oneof.fields) {
if (oneof.fields.indexOf(field) > 0) {
f.print(` } | {`);
}
f.print(makeJsDoc(field, " "));
f.print(f.jsDoc(field, " "));
const { typing } = getFieldTyping(field, f);
f.print(` value: `, typing, `;`);
f.print(` case: "`, localName(field), `";`);
Expand All @@ -131,7 +129,7 @@ function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
}

function generateField(schema: Schema, f: GeneratedFile, field: DescField) {
f.print(makeJsDoc(field, " "));
f.print(f.jsDoc(field, " "));
const e: Printable = [];
e.push(" ", localName(field));
const { typing, optional } = getFieldTyping(field, f);
Expand Down
14 changes: 6 additions & 8 deletions packages/protoc-gen-es/src/javascript.ts
Expand Up @@ -27,9 +27,7 @@ import type {
} from "@bufbuild/protoplugin/ecmascript";
import {
getFieldExplicitDefaultValue,
literalString,
localName,
makeJsDoc,
reifyWkt,
} from "@bufbuild/protoplugin/ecmascript";
import { getNonEditionRuntime } from "./editions.js";
Expand All @@ -51,18 +49,18 @@ export function generateJs(schema: Schema) {
// prettier-ignore
function generateEnum(schema: Schema, f: GeneratedFile, enumeration: DescEnum) {
const protoN = getNonEditionRuntime(schema, enumeration.file);
f.print(makeJsDoc(enumeration));
f.print(f.jsDoc(enumeration));
f.print(f.exportDecl("const", enumeration), " = ", protoN, ".makeEnum(")
f.print(` "`, enumeration.typeName, `",`)
f.print(` [`)
if (enumeration.sharedPrefix === undefined) {
for (const value of enumeration.values) {
f.print(" {no: ", value.number, ", name: ", literalString(value.name), "},")
f.print(" {no: ", value.number, ", name: ", f.string(value.name), "},")
}
} else {
for (const value of enumeration.values) {
const localName = value.name.substring(enumeration.sharedPrefix.length);
f.print(" {no: ", value.number, ", name: ", literalString(value.name), ", localName: ", literalString(localName), "},")
f.print(" {no: ", value.number, ", name: ", f.string(value.name), ", localName: ", f.string(localName), "},")
}
}
f.print(` ],`)
Expand All @@ -73,9 +71,9 @@ function generateEnum(schema: Schema, f: GeneratedFile, enumeration: DescEnum) {
// prettier-ignore
function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage) {
const protoN = getNonEditionRuntime(schema, message.file);
f.print(makeJsDoc(message));
f.print(f.jsDoc(message));
f.print(f.exportDecl("const", message), " = ", protoN, ".makeMessageType(")
f.print(` `, literalString(message.typeName), `,`)
f.print(` `, f.string(message.typeName), `,`)
if (message.fields.length == 0) {
f.print(" [],")
} else {
Expand All @@ -90,7 +88,7 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
.makeMessageType(message.typeName, []).name;
if (needsLocalName) {
// local name is not inferrable from the type name, we need to provide it
f.print(` {localName: `, literalString(localName(message)), `},`)
f.print(` {localName: `, f.string(localName(message)), `},`)
}
f.print(");")
f.print()
Expand Down
16 changes: 7 additions & 9 deletions packages/protoc-gen-es/src/typescript.ts
Expand Up @@ -27,9 +27,7 @@ import type {
import {
getFieldIntrinsicDefaultValue,
getFieldTyping,
literalString,
localName,
makeJsDoc,
reifyWkt,
} from "@bufbuild/protoplugin/ecmascript";
import { generateFieldInfo } from "./javascript.js";
Expand All @@ -52,13 +50,13 @@ export function generateTs(schema: Schema) {
// prettier-ignore
function generateEnum(schema: Schema, f: GeneratedFile, enumeration: DescEnum) {
const protoN = getNonEditionRuntime(schema, enumeration.file);
f.print(makeJsDoc(enumeration));
f.print(f.jsDoc(enumeration));
f.print(f.exportDecl("enum", enumeration), " {");
for (const value of enumeration.values) {
if (enumeration.values.indexOf(value) > 0) {
f.print();
}
f.print(makeJsDoc(value, " "));
f.print(f.jsDoc(value, " "));
f.print(" ", localName(value), " = ", value.number, ",");
}
f.print("}");
Expand All @@ -83,7 +81,7 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
JsonReadOptions,
JsonValue
} = schema.runtime;
f.print(makeJsDoc(message));
f.print(f.jsDoc(message));
f.print(f.exportDecl("class", message), " extends ", Message, "<", message, "> {");
for (const member of message.members) {
switch (member.kind) {
Expand All @@ -103,7 +101,7 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)
f.print();
generateWktMethods(schema, f, message);
f.print(" static readonly runtime: typeof ", protoN, " = ", protoN, ";");
f.print(' static readonly typeName = ', literalString(message.typeName), ';');
f.print(' static readonly typeName = ', f.string(message.typeName), ';');
f.print(" static readonly fields: ", FieldList, " = ", protoN, ".util.newFieldList(() => [");
for (const field of message.fields) {
generateFieldInfo(schema, f, field);
Expand Down Expand Up @@ -141,13 +139,13 @@ function generateMessage(schema: Schema, f: GeneratedFile, message: DescMessage)

// prettier-ignore
function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
f.print(makeJsDoc(oneof, " "));
f.print(f.jsDoc(oneof, " "));
f.print(" ", localName(oneof), ": {");
for (const field of oneof.fields) {
if (oneof.fields.indexOf(field) > 0) {
f.print(` } | {`);
}
f.print(makeJsDoc(field, " "));
f.print(f.jsDoc(field, " "));
const { typing } = getFieldTyping(field, f);
f.print(` value: `, typing, `;`);
f.print(` case: "`, localName(field), `";`);
Expand All @@ -156,7 +154,7 @@ function generateOneof(schema: Schema, f: GeneratedFile, oneof: DescOneof) {
}

function generateField(schema: Schema, f: GeneratedFile, field: DescField) {
f.print(makeJsDoc(field, " "));
f.print(f.jsDoc(field, " "));
const e: Printable = [];
e.push(" ", localName(field));
const { defaultValue, typingInferrable } =
Expand Down
7 changes: 5 additions & 2 deletions packages/protoplugin-test/src/byo-transpile.test.ts
Expand Up @@ -89,7 +89,10 @@ describe("bring your own transpile", () => {
});
const res = plugin.run(req);
expect(res.file.length).toBeGreaterThanOrEqual(1);
const content = res.file[0]?.content ?? "";
return content.trim().split("\n");
let content = res.file[0]?.content ?? "";
if (content.endsWith("\n")) {
content = content.slice(0, -1); // trim final newline so we don't return an extra line
}
return content.split("\n");
}
});
138 changes: 138 additions & 0 deletions packages/protoplugin-test/src/deprecated-jsdoc.test.ts
@@ -0,0 +1,138 @@
// Copyright 2021-2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { describe, expect, test } from "@jest/globals";
import { UpstreamProtobuf } from "upstream-protobuf";
import { CodeGeneratorRequest } from "@bufbuild/protobuf";
import type { Schema } from "@bufbuild/protoplugin/ecmascript";
import { createEcmaScriptPlugin } from "@bufbuild/protoplugin";
import type { GeneratedFile } from "@bufbuild/protoplugin/ecmascript";
import { createJsDocBlock, makeJsDoc } from "@bufbuild/protoplugin/ecmascript";

describe("deprecated makeJsDoc() and createJsDocBlock()", () => {
test("creates JSDoc comment block", async () => {
const lines = await testGenerate(`syntax="proto3";`, (f) => {
f.print(createJsDocBlock(`hello world`));
});
expect(lines).toStrictEqual(["/**", " * hello world", " */"]);
});

test("creates JSDoc comment block for message descriptor", async () => {
const lines = await testGenerate(
`
syntax="proto3";
message SomeMessage {};
`,
(f, schema) => {
f.print(makeJsDoc(schema.files[0].messages[0]));
},
);
expect(lines).toStrictEqual([
"/**",
" * @generated from message SomeMessage",
" */",
]);
});

test("creates JSDoc comment block for message descriptor with comments", async () => {
const lines = await testGenerate(
`
syntax="proto3";
// discarded detached comment
// comment on message
message SomeMessage {};
`,
(f, schema) => {
f.print(makeJsDoc(schema.files[0].messages[0]));
},
);
expect(lines).toStrictEqual([
"/**",
" * comment on message",
" *",
" * @generated from message SomeMessage",
" */",
]);
});

test("indents", async () => {
const lines = await testGenerate(`syntax="proto3";`, (f) => {
f.print(createJsDocBlock(`multi-line\ncomment`, " "));
});
expect(lines).toStrictEqual([
" /**",
" * multi-line",
" * comment",
" */",
]);
});

test("escapes */", async () => {
const lines = await testGenerate(`syntax="proto3";`, (f) => {
f.print(createJsDocBlock(`*/`));
});
expect(lines).toStrictEqual(["/**", " * *\\/", " */"]);
});

test("whitespace is unmodified", async () => {
const lines = await testGenerate(`syntax="proto3";`, (f) => {
f.print(createJsDocBlock(`\na\n b\n c\t`));
});
expect(lines).toStrictEqual([
"/**",
" *",
" * a",
" * b",
" * c\t",
" */",
]);
});

async function testGenerate(
protoContent: string,
gen: (f: GeneratedFile, schema: Schema) => void,
) {
const plugin = createEcmaScriptPlugin({
name: "test",
version: "v1",
generateTs: generateAny,
generateJs: generateAny,
generateDts: generateAny,
});

function generateAny(schema: Schema) {
gen(schema.generateFile("test.ts"), schema);
}

const upstream = new UpstreamProtobuf();
const protoFiles = {
"x.proto": protoContent,
};
const req = CodeGeneratorRequest.fromBinary(
await upstream.createCodeGeneratorRequest(protoFiles, {
parameter: "target=ts",
}),
);
expect(req.protoFile.length).toBe(1);
const res = plugin.run(req);
expect(res.file.length).toBeGreaterThanOrEqual(1);
let content = res.file[0]?.content ?? "";
if (content.endsWith("\n")) {
content = content.slice(0, -1); // trim final newline so we don't return an extra line
}
return content.split("\n");
}
});
7 changes: 5 additions & 2 deletions packages/protoplugin-test/src/file-export-decl.test.ts
Expand Up @@ -95,7 +95,10 @@ describe("file exportDecl", () => {
expect(req.protoFile[0]?.enumType.length).toBe(1);
const res = plugin.run(req);
expect(res.file.length).toBeGreaterThanOrEqual(1);
const content = res.file[0]?.content ?? "";
return content.trim().split("\n");
let content = res.file[0]?.content ?? "";
if (content.endsWith("\n")) {
content = content.slice(0, -1); // trim final newline so we don't return an extra line
}
return content.split("\n");
}
});

0 comments on commit 360bdb9

Please sign in to comment.