diff --git a/.chronus/changes/copilot-fix-emitter-not-found-error-message-2026-7-30-16-11-58.md b/.chronus/changes/copilot-fix-emitter-not-found-error-message-2026-7-30-16-11-58.md new file mode 100644 index 00000000000..250210b31e7 --- /dev/null +++ b/.chronus/changes/copilot-fix-emitter-not-found-error-message-2026-7-30-16-11-58.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Report better error message when specifying an emitter that is not installed with `--emit` flag diff --git a/packages/compiler/src/core/messages.ts b/packages/compiler/src/core/messages.ts index 5e5f832eca3..c91a2528634 100644 --- a/packages/compiler/src/core/messages.ts +++ b/packages/compiler/src/core/messages.ts @@ -805,6 +805,12 @@ const diagnostics = { default: paramMessage`onValidate failed with errors. ${"error"}`, }, }, + "emitter-not-found": { + severity: "error", + messages: { + default: paramMessage`Emitter "${"emitterPackage"}" not found. Make sure to install it with \`npm install ${"emitterPackage"}\`.`, + }, + }, "invalid-emitter": { severity: "error", messages: { diff --git a/packages/compiler/src/core/program.ts b/packages/compiler/src/core/program.ts index 188a1ded0c1..7dcedc5537c 100644 --- a/packages/compiler/src/core/program.ts +++ b/packages/compiler/src/core/program.ts @@ -582,6 +582,7 @@ async function createProgram( async function loadLibrary( basedir: string, libraryNameOrPath: string, + context?: { isEmitter?: boolean }, ): Promise { const [resolution, diagnostics] = await resolveEmitterModuleAndEntrypoint( basedir, @@ -589,7 +590,17 @@ async function createProgram( ); if (resolution === undefined) { - program.reportDiagnostics(diagnostics); + if (context?.isEmitter && diagnostics.some((d) => d.code === "import-not-found")) { + program.reportDiagnostic( + createDiagnostic({ + code: "emitter-not-found", + format: { emitterPackage: libraryNameOrPath }, + target: NoTarget, + }), + ); + } else { + program.reportDiagnostics(diagnostics); + } return undefined; } const { module, entrypoint } = resolution; @@ -610,7 +621,7 @@ async function createProgram( emitterNameOrPath: string, emittersOptions: Record, ): Promise { - const library = await loadLibrary(basedir, emitterNameOrPath); + const library = await loadLibrary(basedir, emitterNameOrPath, { isEmitter: true }); if (library === undefined) { return undefined; diff --git a/packages/compiler/test/core/emitter.test.ts b/packages/compiler/test/core/emitter.test.ts index 300a5f406b1..1112087df5e 100644 --- a/packages/compiler/test/core/emitter.test.ts +++ b/packages/compiler/test/core/emitter.test.ts @@ -6,7 +6,7 @@ import { TypeSpecLibrary, TypeSpecLibraryDef, } from "../../src/index.js"; -import { expectDiagnosticEmpty } from "../../src/testing/expect.js"; +import { expectDiagnosticEmpty, expectDiagnostics } from "../../src/testing/expect.js"; import { mockFile } from "../../src/testing/fs.js"; import { Tester } from "../tester.js"; @@ -109,3 +109,16 @@ it("when using dry-run only call emitter with the capabilities", async () => { expect(emitter2.$onEmit).not.toHaveBeenCalled(); expect(emitter3.$onEmit).not.toHaveBeenCalled(); }); + +it("reports emitter-not-found when emitter package is not installed", async () => { + const diagnostics = await Tester.diagnose("model Foo {}", { + compilerOptions: { + emit: ["not-installed-emitter"], + }, + }); + + expectDiagnostics(diagnostics, { + code: "emitter-not-found", + message: `Emitter "not-installed-emitter" not found. Make sure to install it with \`npm install not-installed-emitter\`.`, + }); +});