diff --git a/packages/protoplugin-test/src/transpile.test.ts.ts b/packages/protoplugin-test/src/transpile.test.ts similarity index 64% rename from packages/protoplugin-test/src/transpile.test.ts.ts rename to packages/protoplugin-test/src/transpile.test.ts index b0048d718..0e7bfd7f4 100644 --- a/packages/protoplugin-test/src/transpile.test.ts.ts +++ b/packages/protoplugin-test/src/transpile.test.ts @@ -97,4 +97,66 @@ describe("built-in transpile", () => { ]); }); }); + + describe("failing to emit", () => { + test("raises error with helpful message", async () => { + await expect(async () => + testTranspileToDts([ + `export interface Foo {`, + ` p: {`, + ` [K in keyof P]: string;`, + ` },`, + `}`, + ]), + ).rejects.toThrow( + /^A problem occurred during transpilation and files were not generated\. {2}Contact the plugin author for support\.\n/, + ); + }); + test("raises error with diagnostics", async () => { + await expect(async () => + testTranspileToDts([ + `export interface Foo {`, + ` p: {`, + ` [K in keyof P]: string;`, + ` },`, + `}`, + ]), + ).rejects.toThrow( + /test\.ts\(3,17\): error TS4033: Property 'p' of exported interface has or is using private name 'P'\.$/, + ); + }); + test("raises error with 3 diagnostics, and elides the rest", async () => { + await expect(async () => + testTranspileToDts([ + `export interface Foo1 {`, + ` p: {`, + ` [K in keyof P]: string;`, + ` },`, + `}`, + `export interface Foo2 {`, + ` p: {`, + ` [K in keyof P]: string;`, + ` },`, + `}`, + `export interface Foo3 {`, + ` p: {`, + ` [K in keyof P]: string;`, + ` },`, + `}`, + `export interface Foo4 {`, + ` p: {`, + ` [K in keyof P]: string;`, + ` },`, + `}`, + `export interface Foo5 {`, + ` p: {`, + ` [K in keyof P]: string;`, + ` },`, + `}`, + ]), + ).rejects.toThrow( + /(?:test\.ts\(\d+,\d+\): .+\n){3}2 more diagnostics elided/, + ); + }); + }); }); diff --git a/packages/protoplugin/src/transpile.ts b/packages/protoplugin/src/transpile.ts index cb7e49092..9b5c936cb 100644 --- a/packages/protoplugin/src/transpile.ts +++ b/packages/protoplugin/src/transpile.ts @@ -158,9 +158,35 @@ export function transpile( throw err; } if (result.emitSkipped) { + // When compilation fails, this error message is printed to stderr. + const diagnostics = formatDiagnostics(result.diagnostics); throw Error( - "A problem occurred during transpilation and files were not generated. Contact the plugin author for support.", + `A problem occurred during transpilation and files were not generated. Contact the plugin author for support.\n\n${diagnostics}`, ); } return results; } + +function formatDiagnostics(diagnostics: readonly ts.Diagnostic[]): string { + const sorted = ts.sortAndDeduplicateDiagnostics(diagnostics); + if (sorted.length == 0) { + return ""; + } + const first = sorted.slice(0, 3); + const formatHost: ts.FormatDiagnosticsHost = { + getCanonicalFileName(fileName: string): string { + return fileName; + }, + getCurrentDirectory(): string { + return "."; + }, + getNewLine(): string { + return "\n"; + }, + }; + let out = ts.formatDiagnostics(first, formatHost).trim(); + if (first.length < sorted.length) { + out += `\n${sorted.length - first.length} more diagnostics elided`; + } + return out; +}