diff --git a/packages/quicktype-core/src/Messages.ts b/packages/quicktype-core/src/Messages.ts index 902c3b187c..0b6b8b2bd9 100644 --- a/packages/quicktype-core/src/Messages.ts +++ b/packages/quicktype-core/src/Messages.ts @@ -125,6 +125,7 @@ export type ErrorProperties = properties: { dir: string }; } | { kind: "DriverCannotMixNonJSONInputs"; properties: { dir: string } } + | { kind: "DriverNoSamplesForTopLevel"; properties: { name: string } } | { kind: "DriverUnknownDebugOption"; properties: { option: string } } | { kind: "DriverNoLanguageOrExtension"; properties: Record } | { kind: "DriverCLIOptionParsingFailed"; properties: { message: string } } @@ -239,6 +240,7 @@ const errorMessages: ErrorMessages = { "Cannot mix JSON samples with JSON Schems, GraphQL, or TypeScript in input subdirectory ${dir}", DriverCannotMixNonJSONInputs: "Cannot mix JSON Schema, GraphQL, and TypeScript in an input subdirectory ${dir}", + DriverNoSamplesForTopLevel: "No JSON samples given for top-level ${name}", DriverUnknownDebugOption: "Unknown debug option ${option}", DriverNoLanguageOrExtension: "Please specify a language (--lang) or an output file extension", diff --git a/packages/quicktype-core/src/input/Inputs.ts b/packages/quicktype-core/src/input/Inputs.ts index b42380eda7..1419fd19b9 100644 --- a/packages/quicktype-core/src/input/Inputs.ts +++ b/packages/quicktype-core/src/input/Inputs.ts @@ -120,6 +120,10 @@ export class JSONInput implements Input> { public async addSource(source: JSONSourceData): Promise { const { name, samples, description } = source; + if (samples.length === 0) { + return messageError("DriverNoSamplesForTopLevel", { name }); + } + try { const values = await arrayMapSync( samples, @@ -133,6 +137,10 @@ export class JSONInput implements Input> { public addSourceSync(source: JSONSourceData): void { const { name, samples, description } = source; + if (samples.length === 0) { + return messageError("DriverNoSamplesForTopLevel", { name }); + } + try { const values = samples.map((s) => this._compressedJSON.parseSync(s), diff --git a/test/unit/empty-json-samples.test.ts b/test/unit/empty-json-samples.test.ts new file mode 100644 index 0000000000..052c466478 --- /dev/null +++ b/test/unit/empty-json-samples.test.ts @@ -0,0 +1,44 @@ +// addSource with an empty samples array used to succeed silently and +// quicktype would render full converter boilerplate for a type with no +// evidence whatsoever. An empty samples array is almost always a caller bug +// (a glob that matched nothing, a filtered list that came up empty), so it +// must fail loudly instead — see +// https://github.com/glideapps/quicktype/issues/2934. + +import { describe, expect, test } from "vitest"; + +import { + InputData, + jsonInputForTargetLanguage, + quicktype, +} from "../../packages/quicktype-core/src/index.js"; + +describe("JSON input with empty samples", () => { + test("addSource throws a clear error", async () => { + const jsonInput = jsonInputForTargetLanguage("typescript"); + await expect( + jsonInput.addSource({ name: "X", samples: [] }), + ).rejects.toThrow("No JSON samples given for top-level X"); + }); + + test("addSourceSync throws a clear error", () => { + const jsonInput = jsonInputForTargetLanguage("typescript"); + expect(() => + jsonInput.addSourceSync({ name: "X", samples: [] }), + ).toThrow("No JSON samples given for top-level X"); + }); + + test("non-empty samples still generate code", async () => { + const jsonInput = jsonInputForTargetLanguage("typescript"); + await jsonInput.addSource({ + name: "Person", + samples: ['{"name":"Alice","age":30}'], + }); + + const inputData = new InputData(); + inputData.addInput(jsonInput); + const result = await quicktype({ inputData, lang: "typescript" }); + + expect(result.lines.join("\n")).toContain("Person"); + }); +});