From 742bbcee8a384adfc486284e69802e8095052527 Mon Sep 17 00:00:00 2001 From: blorgon1 <102304575+blorgon1@users.noreply.github.com> Date: Wed, 3 May 2023 13:25:10 +0000 Subject: [PATCH 1/7] feat: output index files --- src/options.ts | 2 ++ src/plugin.ts | 10 ++++++++-- src/utils.ts | 46 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/options.ts b/src/options.ts index 513d27095..ef341d90c 100644 --- a/src/options.ts +++ b/src/options.ts @@ -80,6 +80,7 @@ export type Options = { useReadonlyTypes: boolean; useSnakeTypeName: boolean; outputExtensions: boolean; + outputIndex: boolean; M: { [from: string]: string }; }; @@ -130,6 +131,7 @@ export function defaultOptions(): Options { useReadonlyTypes: false, useSnakeTypeName: true, outputExtensions: false, + outputIndex: false, M: {}, }; } diff --git a/src/plugin.ts b/src/plugin.ts index dd84b07f6..cd5d577a6 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -2,10 +2,9 @@ import { CodeGeneratorRequest, CodeGeneratorResponse, CodeGeneratorResponse_Feature, - FileDescriptorProto, } from "ts-proto-descriptors"; import { promisify } from "util"; -import { prefixDisableLinter, protoFilesToGenerate, readToBuffer } from "./utils"; +import { generateIndexFiles, protoFilesToGenerate, readToBuffer } from "./utils"; import { generateFile, makeUtils } from "./main"; import { createTypeMap } from "./types"; import { Context } from "./context"; @@ -46,6 +45,13 @@ async function main() { files.push({ name: path, content }); } + if (options.outputIndex) { + for (const [path, code] of generateIndexFiles(filesToGenerate, options)) { + const content = code.toString({ ...getTsPoetOpts(options), path }); + files.push({ name: path, content }); + } + } + const response = CodeGeneratorResponse.fromPartial({ file: files, supportedFeatures: CodeGeneratorResponse_Feature.FEATURE_PROTO3_OPTIONAL, diff --git a/src/utils.ts b/src/utils.ts index 9ba1a5956..641a58d9b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ -import { code, Code, imp, Import } from "ts-poet"; +import * as path from 'path'; +import { code, Code, imp, Import, joinCode } from "ts-poet"; import { CodeGeneratorRequest, FieldDescriptorProto, @@ -15,6 +16,49 @@ export function protoFilesToGenerate(request: CodeGeneratorRequest): FileDescrip return request.protoFile.filter((f) => request.fileToGenerate.includes(f.name)); } +type PackageTree = { + index: string, + chunks: Code[], + leaves: { [k: string]: PackageTree }, +}; +export function generateIndexFiles(files: FileDescriptorProto[], options: Options): [string, Code][] { + const packageTree: PackageTree = { + index: "index.ts", + leaves: {}, + chunks: [], + }; + for (const { name, package: pkg } of files) { + const moduleName = name.replace(".proto", options.fileSuffix); + const importName = camelCaseGrpc(path.basename(name, ".proto")); + const pkgParts = pkg.split('.'); + + const branch = pkgParts.reduce((branch, part, i): PackageTree => { + if (!(part in branch.leaves)) { + const prePkgParts = pkgParts.slice(0, i + 1); + const index = `index.${prePkgParts.join('.')}.ts`; + branch.chunks.push(code`export * as ${part} from "./${path.basename(index, ".ts")}";`); + branch.leaves[part] = { + index, + leaves: {}, + chunks: [], + }; + } + return branch.leaves[part]; + }, packageTree); + branch.chunks.push(code`export * as ${importName} from "./${moduleName}";`); + } + + const indexFiles: [string, Code][] = []; + let branches: PackageTree[] = [packageTree]; + let currentBranch; + while (currentBranch = branches.pop()) { + indexFiles.push([currentBranch.index, joinCode(currentBranch.chunks)]); + branches.push(...Object.values(currentBranch.leaves)); + } + + return indexFiles; +} + export function readToBuffer(stream: ReadStream): Promise { return new Promise((resolve) => { const ret: Array = []; From 14c392b662faf2653928f22152eaa78cc0b7c30b Mon Sep 17 00:00:00 2001 From: blorgon1 <102304575+blorgon1@users.noreply.github.com> Date: Wed, 3 May 2023 13:27:49 +0000 Subject: [PATCH 2/7] chore: add outputIndex to readme --- README.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.markdown b/README.markdown index 3f43bc0a9..669af7b54 100644 --- a/README.markdown +++ b/README.markdown @@ -509,6 +509,8 @@ Generated code will be placed in the Gradle build directory. Extension encode/decode methods are compliant with the `outputEncodeMethods` option, and if `unknownFields=true`, the `setExtension` and `getExtension` methods will be created for extendable messages, also compliant with `outputEncodeMethods` (setExtension = encode, getExtension = decode). +- With `--ts_proto_opt=outputIndex=true`, index files will be generated based on the proto package namespaces. + ### NestJS Support We have a great way of working together with [nestjs](https://docs.nestjs.com/microservices/grpc). `ts-proto` generates `interfaces` and `decorators` for you controller, client. For more information see the [nestjs readme](NESTJS.markdown). From a38821785d48cf761c2328052125276291d3ace8 Mon Sep 17 00:00:00 2001 From: blorgon1 <102304575+blorgon1@users.noreply.github.com> Date: Wed, 3 May 2023 16:01:36 +0000 Subject: [PATCH 3/7] feat: remove extra index nesting --- src/options.ts | 4 ++++ src/utils.ts | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/options.ts b/src/options.ts index ef341d90c..925496534 100644 --- a/src/options.ts +++ b/src/options.ts @@ -220,6 +220,10 @@ export function optionsFromParameter(parameter: string | undefined): Options { options.initializeFieldsAsUndefined = false; } + if (options.outputIndex) { + options.exportCommonSymbols = false; + } + return options; } diff --git a/src/utils.ts b/src/utils.ts index 641a58d9b..0160b85f4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -29,7 +29,6 @@ export function generateIndexFiles(files: FileDescriptorProto[], options: Option }; for (const { name, package: pkg } of files) { const moduleName = name.replace(".proto", options.fileSuffix); - const importName = camelCaseGrpc(path.basename(name, ".proto")); const pkgParts = pkg.split('.'); const branch = pkgParts.reduce((branch, part, i): PackageTree => { @@ -45,7 +44,7 @@ export function generateIndexFiles(files: FileDescriptorProto[], options: Option } return branch.leaves[part]; }, packageTree); - branch.chunks.push(code`export * as ${importName} from "./${moduleName}";`); + branch.chunks.push(code`export * from "./${moduleName}";`); } const indexFiles: [string, Code][] = []; From ebc369e06d54d4990c8e61d8fcf60f5e25326e1d Mon Sep 17 00:00:00 2001 From: blorgon1 <102304575+blorgon1@users.noreply.github.com> Date: Wed, 3 May 2023 16:02:42 +0000 Subject: [PATCH 4/7] chore: update readme --- README.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.markdown b/README.markdown index 669af7b54..98453b1ac 100644 --- a/README.markdown +++ b/README.markdown @@ -511,6 +511,8 @@ Generated code will be placed in the Gradle build directory. - With `--ts_proto_opt=outputIndex=true`, index files will be generated based on the proto package namespaces. + This will disable `exportCommonSymbols` to avoid name collisions on the common symbols. + ### NestJS Support We have a great way of working together with [nestjs](https://docs.nestjs.com/microservices/grpc). `ts-proto` generates `interfaces` and `decorators` for you controller, client. For more information see the [nestjs readme](NESTJS.markdown). From 62fa8fcd9e65eb01ad10ba3707efce6cc0279828 Mon Sep 17 00:00:00 2001 From: blorgon1 <102304575+blorgon1@users.noreply.github.com> Date: Wed, 3 May 2023 22:06:17 +0000 Subject: [PATCH 5/7] test: output-index --- integration/codegen.ts | 11 +++- integration/output-index/a-test.ts | 14 +++++ integration/output-index/a.bin | Bin 0 -> 173 bytes integration/output-index/a.proto | 6 ++ integration/output-index/a.ts | 77 ++++++++++++++++++++++++ integration/output-index/index.base.ts | 3 + integration/output-index/index.ts | 3 + integration/output-index/parameters.txt | 1 + src/utils.ts | 2 +- 9 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 integration/output-index/a-test.ts create mode 100644 integration/output-index/a.bin create mode 100644 integration/output-index/a.proto create mode 100644 integration/output-index/a.ts create mode 100644 integration/output-index/index.base.ts create mode 100644 integration/output-index/index.ts create mode 100644 integration/output-index/parameters.txt diff --git a/integration/codegen.ts b/integration/codegen.ts index 900ee1a1f..529c27f36 100644 --- a/integration/codegen.ts +++ b/integration/codegen.ts @@ -4,7 +4,7 @@ import { parse } from "path"; import { promisify } from "util"; import { generateFile, makeUtils } from "../src/main"; import { createTypeMap } from "../src/types"; -import { prefixDisableLinter } from "../src/utils"; +import { generateIndexFiles } from "../src/utils"; import { getTsPoetOpts, optionsFromParameter } from "../src/options"; import { Context } from "../src/context"; import { generateTypeRegistry } from "../src/generate-type-registry"; @@ -56,6 +56,15 @@ async function generate(binFile: string, baseDir: string, parameter: string) { await promisify(writeFile)(filePath, code.toString({ ...getTsPoetOpts(options), path })); } + + if (options.outputIndex) { + for (const [path, code] of generateIndexFiles(request.protoFile, options)) { + const filePath = `${baseDir}/${path}`; + const dirPath = parse(filePath).dir; + await promisify(mkdir)(dirPath, { recursive: true }).catch(() => {}); + await promisify(writeFile)(filePath, code.toString({ ...getTsPoetOpts(options), path })); + } + } } main().then(() => { diff --git a/integration/output-index/a-test.ts b/integration/output-index/a-test.ts new file mode 100644 index 000000000..a81065ced --- /dev/null +++ b/integration/output-index/a-test.ts @@ -0,0 +1,14 @@ +import * as Index from '.'; + +describe('output-index', () => { + it('generates index files correctly', () => { + expect(Index).toMatchObject({ + base: { + A: { + encode: expect.any(Function), + decode: expect.any(Function), + }, + }, + }); + }); +}); diff --git a/integration/output-index/a.bin b/integration/output-index/a.bin new file mode 100644 index 0000000000000000000000000000000000000000..1d87fd20760a5e226b205fa025b37768c6e2aa14 GIT binary patch literal 173 zcmXxeJr2S!3a<2i*Q{Uq-_4)dxNpar^j=8 zPx}yLv+KC0&zDhC(;>gceha=>0_Tnoe2h2F*E>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.a = reader.string(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): A { + return { a: isSet(object.a) ? String(object.a) : "" }; + }, + + toJSON(message: A): unknown { + const obj: any = {}; + message.a !== undefined && (obj.a = message.a); + return obj; + }, + + create, I>>(base?: I): A { + return A.fromPartial(base ?? {}); + }, + + fromPartial, I>>(object: I): A { + const message = createBaseA(); + message.a = object.a ?? ""; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +type DeepPartial = T extends Builtin ? T + : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +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/integration/output-index/index.base.ts b/integration/output-index/index.base.ts new file mode 100644 index 000000000..b75bfa97f --- /dev/null +++ b/integration/output-index/index.base.ts @@ -0,0 +1,3 @@ +/* eslint-disable */ + +export * from "./a"; diff --git a/integration/output-index/index.ts b/integration/output-index/index.ts new file mode 100644 index 000000000..c41c2e7e5 --- /dev/null +++ b/integration/output-index/index.ts @@ -0,0 +1,3 @@ +/* eslint-disable */ + +export * as base from "./index.base"; diff --git a/integration/output-index/parameters.txt b/integration/output-index/parameters.txt new file mode 100644 index 000000000..855f60601 --- /dev/null +++ b/integration/output-index/parameters.txt @@ -0,0 +1 @@ +outputIndex=true \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 0160b85f4..6935d14f3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -29,7 +29,7 @@ export function generateIndexFiles(files: FileDescriptorProto[], options: Option }; for (const { name, package: pkg } of files) { const moduleName = name.replace(".proto", options.fileSuffix); - const pkgParts = pkg.split('.'); + const pkgParts = pkg.length > 0 ? pkg.split('.') : []; const branch = pkgParts.reduce((branch, part, i): PackageTree => { if (!(part in branch.leaves)) { From 2f029a57e17492ade21f5ca8e0e8ea5b65cac078 Mon Sep 17 00:00:00 2001 From: blorgon1 <102304575+blorgon1@users.noreply.github.com> Date: Mon, 22 May 2023 15:43:56 +0000 Subject: [PATCH 6/7] fix: lint --- src/plugin.ts | 6 +----- src/utils.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index cd5d577a6..580f6b1e2 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,8 +1,4 @@ -import { - CodeGeneratorRequest, - CodeGeneratorResponse, - CodeGeneratorResponse_Feature, -} from "ts-proto-descriptors"; +import { CodeGeneratorRequest, CodeGeneratorResponse, CodeGeneratorResponse_Feature } from "ts-proto-descriptors"; import { promisify } from "util"; import { generateIndexFiles, protoFilesToGenerate, readToBuffer } from "./utils"; import { generateFile, makeUtils } from "./main"; diff --git a/src/utils.ts b/src/utils.ts index 6935d14f3..df6e55008 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import * as path from 'path'; +import * as path from "path"; import { code, Code, imp, Import, joinCode } from "ts-poet"; import { CodeGeneratorRequest, @@ -17,9 +17,9 @@ export function protoFilesToGenerate(request: CodeGeneratorRequest): FileDescrip } type PackageTree = { - index: string, - chunks: Code[], - leaves: { [k: string]: PackageTree }, + index: string; + chunks: Code[]; + leaves: { [k: string]: PackageTree }; }; export function generateIndexFiles(files: FileDescriptorProto[], options: Options): [string, Code][] { const packageTree: PackageTree = { @@ -29,12 +29,12 @@ export function generateIndexFiles(files: FileDescriptorProto[], options: Option }; for (const { name, package: pkg } of files) { const moduleName = name.replace(".proto", options.fileSuffix); - const pkgParts = pkg.length > 0 ? pkg.split('.') : []; + const pkgParts = pkg.length > 0 ? pkg.split(".") : []; const branch = pkgParts.reduce((branch, part, i): PackageTree => { if (!(part in branch.leaves)) { const prePkgParts = pkgParts.slice(0, i + 1); - const index = `index.${prePkgParts.join('.')}.ts`; + const index = `index.${prePkgParts.join(".")}.ts`; branch.chunks.push(code`export * as ${part} from "./${path.basename(index, ".ts")}";`); branch.leaves[part] = { index, @@ -50,7 +50,7 @@ export function generateIndexFiles(files: FileDescriptorProto[], options: Option const indexFiles: [string, Code][] = []; let branches: PackageTree[] = [packageTree]; let currentBranch; - while (currentBranch = branches.pop()) { + while ((currentBranch = branches.pop())) { indexFiles.push([currentBranch.index, joinCode(currentBranch.chunks)]); branches.push(...Object.values(currentBranch.leaves)); } From 00a08714653b8cf2ac8b462605a1bbc8b00bd2d4 Mon Sep 17 00:00:00 2001 From: blorgon1 <102304575+blorgon1@users.noreply.github.com> Date: Mon, 22 May 2023 15:46:16 +0000 Subject: [PATCH 7/7] fix: options-test snapshot --- tests/options-test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/options-test.ts b/tests/options-test.ts index f9c4f655f..45c0337b2 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -26,6 +26,7 @@ describe("options", () => { "outputClientImpl": false, "outputEncodeMethods": false, "outputExtensions": false, + "outputIndex": false, "outputJsonMethods": true, "outputPartialMethods": false, "outputSchema": false,