From 87f3ae329c053a04d51be5be133506ec7893d7a6 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 19:17:04 -0400 Subject: [PATCH 1/7] fix(json-schema): stop merged type descriptions overriding property descriptions (#2801) quicktype's type unifier merges structurally identical types (e.g. every bare map-of-string like {additionalProperties: string}) into one shared type node, and set-unions their `description` type attributes. In JSONSchemaRenderer.definitionForObject, that merged type-level description was applied to an inlined property schema before the correct, per-property description was considered, so unrelated descriptions got concatenated onto properties that share a structural shape with other, unrelated properties elsewhere in the schema. Fix: always prefer the property-specific description (descriptionForClassProperty) over the type-level one when rendering an object's properties, falling back to the type-level description only when no property-specific one exists. Adds a schema-to-schema regression fixture (schema-schema) that asserts on the description text of generated JSON Schema output, which the existing JSONSchemaFixture/JSONSchemaJSONFixture classes cannot express. Co-Authored-By: gpt-5.6-sol via pi --- .github/workflows/test-pr.yaml | 2 +- .../language/JSONSchema/JSONSchemaRenderer.ts | 10 ++- test/fixtures.ts | 71 +++++++++++++++++++ .../schema/description-unification.schema | 34 +++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 test/inputs/schema/description-unification.schema diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 7001f80230..03199e6804 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -36,7 +36,7 @@ jobs: matrix: fixture: - typescript,typescript-zod,schema-typescript-zod,typescript-effect-schema - - javascript,schema-javascript + - javascript,schema-javascript,schema-schema - golang,schema-golang - cjson,schema-cjson - cjson-default,cjson-multi-header,cjson-multi-split diff --git a/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts b/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts index 227a46da75..0b1745fb57 100644 --- a/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts +++ b/packages/quicktype-core/src/language/JSONSchema/JSONSchemaRenderer.ts @@ -128,12 +128,10 @@ export class JSONSchemaRenderer extends ConvenienceRenderer { const req: string[] = []; for (const [name, p] of o.getProperties()) { const prop = this.schemaForType(p.type); - if (prop.description === undefined) { - addDescriptionToSchema( - prop, - this.descriptionForClassProperty(o, name), - ); - } + addDescriptionToSchema( + prop, + this.descriptionForClassProperty(o, name), + ); props[name] = prop; if (!p.isOptional) { diff --git a/test/fixtures.ts b/test/fixtures.ts index b7ec881474..aef1c05aac 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -896,6 +896,76 @@ class JSONSchemaFixture extends LanguageFixture { } } +// Regression fixture for #2801. It verifies schema-specific output that +// cannot be observed by compiling and running code generated from a JSON Schema. +class JSONSchemaToSchemaFixture extends Fixture { + readonly name = "schema-schema"; + + constructor() { + super(languages.TypeScriptLanguage); + } + + getSamples(sources: string[]): { priority: Sample[]; others: Sample[] } { + return samplesFromSources( + sources, + ["test/inputs/schema/description-unification.schema"], + [], + "schema", + ); + } + + async runWithSample( + sample: Sample, + index: number, + total: number, + ): Promise { + const cwd = this.getRunDirectory(); + const message = this.runMessageStart(sample, index, total, cwd, false); + const output = path.join(cwd, "schema.json"); + mkdirs(cwd); + + await quicktype({ + src: [sample.path], + srcLang: "schema", + lang: "schema", + topLevel: "DescriptionUnification", + out: output, + telemetry: "disable", + }); + + const schema = JSON.parse(fs.readFileSync(output, "utf8")) as { + definitions: Record< + string, + { properties: Record } + >; + }; + const expectedDescriptions = [ + ["Allow", "labels", "The labels to apply to the policy"], + [ + "Metadata", + "annotations", + "Additional annotations for the generated resource", + ], + ] as const; + + for (const [definition, property, expected] of expectedDescriptions) { + const actual = + schema.definitions[definition]?.properties[property] + ?.description; + if (actual !== expected) { + failWith("Schema property has the wrong description", { + definition, + property, + expected, + actual, + }); + } + } + + this.runMessageEnd(message, expectedDescriptions.length); + } +} + type TreeSitterTarget = { displayName: string; language: languages.Language; @@ -1664,6 +1734,7 @@ export const allFixtures: Fixture[] = [ new JSONFixture(languages.ElixirLanguage), new JSONSchemaJSONFixture(languages.CSharpLanguage), new JSONTypeScriptFixture(languages.CSharpLanguage), + new JSONSchemaToSchemaFixture(), // new JSONSchemaFixture(languages.CrystalLanguage), new JSONSchemaFixture(languages.CSharpLanguage), new JSONSchemaFixture( diff --git a/test/inputs/schema/description-unification.schema b/test/inputs/schema/description-unification.schema new file mode 100644 index 0000000000..a3539dba1e --- /dev/null +++ b/test/inputs/schema/description-unification.schema @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DescriptionUnification", + "type": "object", + "properties": { + "allow": { + "type": "object", + "properties": { + "labels": { + "description": "The labels to apply to the policy", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["labels"] + }, + "metadata": { + "type": "object", + "properties": { + "annotations": { + "description": "Additional annotations for the generated resource", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["annotations"] + } + }, + "required": ["allow", "metadata"] +} From 486a0b1a37669552625db28aa1af782c4035d8c2 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 19:36:45 -0400 Subject: [PATCH 2/7] test: add missing fixture cases for description-unification.schema (#3056) Co-Authored-By: Claude --- ...description-unification.1.fail.no-defaults.json | 7 +++++++ test/inputs/schema/description-unification.1.json | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 test/inputs/schema/description-unification.1.fail.no-defaults.json create mode 100644 test/inputs/schema/description-unification.1.json diff --git a/test/inputs/schema/description-unification.1.fail.no-defaults.json b/test/inputs/schema/description-unification.1.fail.no-defaults.json new file mode 100644 index 0000000000..b3204df261 --- /dev/null +++ b/test/inputs/schema/description-unification.1.fail.no-defaults.json @@ -0,0 +1,7 @@ +{ + "allow": { + "labels": { + "env": "production" + } + } +} diff --git a/test/inputs/schema/description-unification.1.json b/test/inputs/schema/description-unification.1.json new file mode 100644 index 0000000000..c9991b0071 --- /dev/null +++ b/test/inputs/schema/description-unification.1.json @@ -0,0 +1,14 @@ +{ + "allow": { + "labels": { + "env": "production", + "team": "core" + } + }, + "metadata": { + "annotations": { + "note": "generated by quicktype", + "owner": "platform" + } + } +} From 2df96b60024e885d5afebbe85bebfbdfc5fdd694 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 20:52:34 -0400 Subject: [PATCH 3/7] fix CI: skip description-unification.schema for cjson (#undefined) cJSON's generated deserializer does not validate absent required properties (the same known limitation that already skips required.schema and intersection.schema for this language), so the newly-added description-unification.1.fail.no-defaults.json negative sample round-trips instead of being rejected, failing the schema-cjson fixture. Co-Authored-By: gpt-5.6-sol via pi --- test/languages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/languages.ts b/test/languages.ts index 70fa741ebf..e3bef26590 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -613,6 +613,7 @@ export const CJSONLanguage: Language = { /* Required properties absent are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */ "intersection.schema", "required.schema", + "description-unification.schema", /* Pure Any type not supported (for the current implementation, can be added later, should manage a callback to provide the final application a way to handle it at parsing and creation of cJSON) */ "any.schema", "direct-union.schema", From d4637256a27d62e672abbadadeebf284fd13e985 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 21:05:42 -0400 Subject: [PATCH 4/7] fix CI: update stale cjson-enum-default expectation to declaration order (#undefined) Merging master into agent/fix-issue-2801 surfaced a pre-existing master failure unrelated to this PR's own diff: PR #2357 added cjson-enum-default.test.ts asserting an alphabetically-ordered enum (SUBSCRIPTION_CONFIG, HEARTBEAT, STATE), but PR #1289 landed afterward and intentionally stopped ConvenienceRenderer.forEachEnumCase from alphabetizing enum cases, so cJSON now emits them in declaration order (SUBSCRIPTION_STATE, CONFIG, HEARTBEAT). Nobody updated the stale test expectation, so it has been failing on master HEAD itself since #1289 merged. Update the expected enum layout to match the correct, intended declaration-order behavior while keeping the actual behavior under test (the invalid/absent value 0 never collides with a real enumerator, which start at 1) unchanged. Co-Authored-By: gpt-5.6-sol via pi --- test/unit/cjson-enum-default.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/cjson-enum-default.test.ts b/test/unit/cjson-enum-default.test.ts index b2c02d60ee..0ecd24cb92 100644 --- a/test/unit/cjson-enum-default.test.ts +++ b/test/unit/cjson-enum-default.test.ts @@ -28,9 +28,9 @@ describe("cJSON enum invalid value", () => { const output = await cJSONOutput(); expect(output).toContain(`enum Subscription { - SUBSCRIPTION_CONFIG = 1, + SUBSCRIPTION_STATE = 1, + SUBSCRIPTION_CONFIG, SUBSCRIPTION_HEARTBEAT, - SUBSCRIPTION_STATE, };`); expect(output).toContain("enum Subscription x = 0;"); }); From d1b9ccbad726e7e4dd9761deceb832e75f817d9a Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 21:15:55 -0400 Subject: [PATCH 5/7] fix CI: rename schema-schema regression fixture to avoid name collision (#undefined) Merging master into agent/fix-issue-2801 surfaced another pre-existing-vs-new conflict: this PR's own JSONSchemaToSchemaFixture (added in #2801) is named "schema-schema", and master's PR #2557 independently added `new JSONSchemaFixture(languages.JSONSchemaLanguage)`, whose default name (`schema-${language.name}`) computes to the SAME "schema-schema" string. Running FIXTURE=schema-schema (as the CI matrix job does) ran both fixtures under one name, and the description-unification regression assertion added by this PR failed intermittently when interleaved with the unrelated fixture's many samples, even though the same quicktype call in isolation produces the correct description. Rename this PR's regression fixture to "schema-description-unification" (unique, verified against all other registered fixture names) and update the CI matrix in .github/workflows/test-pr.yaml to match. No production source or other fixtures touched. Co-Authored-By: gpt-5.6-sol via pi --- .github/workflows/test-pr.yaml | 2 +- test/fixtures.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 03199e6804..5adffe5fa3 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -36,7 +36,7 @@ jobs: matrix: fixture: - typescript,typescript-zod,schema-typescript-zod,typescript-effect-schema - - javascript,schema-javascript,schema-schema + - javascript,schema-javascript,schema-description-unification - golang,schema-golang - cjson,schema-cjson - cjson-default,cjson-multi-header,cjson-multi-split diff --git a/test/fixtures.ts b/test/fixtures.ts index b7fd4aeb14..dd64b79b24 100644 --- a/test/fixtures.ts +++ b/test/fixtures.ts @@ -899,7 +899,7 @@ class JSONSchemaFixture extends LanguageFixture { // Regression fixture for #2801. It verifies schema-specific output that // cannot be observed by compiling and running code generated from a JSON Schema. class JSONSchemaToSchemaFixture extends Fixture { - readonly name = "schema-schema"; + readonly name = "schema-description-unification"; constructor() { super(languages.TypeScriptLanguage); From d283ce58cda061b8e387ee6a36400d7976857e02 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 21:23:58 -0400 Subject: [PATCH 6/7] fix CI: skip description-unification.schema for Elixir (#undefined) Elixir's generated deserializer does not enforce required struct keys at runtime (missing required properties are just set to nil), the same already-documented limitation that skips required.schema, intersection.schema, and strict-optional.schema for this language. The newly-added description-unification.1.fail.no-defaults.json negative sample round-trips instead of being rejected, failing the schema-elixir fixture. Skip it for the same reason, following the identical fix already applied for cJSON. Co-Authored-By: gpt-5.6-sol via pi --- test/languages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/languages.ts b/test/languages.ts index ea42c8f2ec..46e6f54f71 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -2212,6 +2212,7 @@ export const ElixirLanguage: Language = { "strict-optional.schema", "required.schema", "intersection.schema", + "description-unification.schema", // The test incorrectly succeeds due to the emitter being permissive for unions that contain only primitives. A future enhancement // for the Elixir emitter could be a user-controlled 'strict' mode that pattern matches even on unions of only primitive types. From 081fddd65e44b7ce0fb695259d85b1459c5ce6ef Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 20 Jul 2026 21:26:38 -0400 Subject: [PATCH 7/7] fix CI: proactively skip description-unification.schema for Haskell (#undefined) Haskell's test driver encodes decode results as Maybe, so a failed decode prints "null" and exits 0 -- expected-failure samples for missing required properties cannot be detected. This is the same documented limitation that already skips required.schema and required-non-properties.schema for this language. The newly-added description-unification.1.fail.no-defaults.json negative sample is exactly this class of missing-required-property case, so fix it proactively (Haskell's job was cancelled by fail-fast before reaching this schema in the last run, but the root cause is identical to required.schema). Co-Authored-By: gpt-5.6-sol via pi --- test/languages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/languages.ts b/test/languages.ts index 46e6f54f71..5a839da8fb 100644 --- a/test/languages.ts +++ b/test/languages.ts @@ -1868,6 +1868,7 @@ export const HaskellLanguage: Language = { "keyword-unions.schema", "optional-any.schema", "required.schema", + "description-unification.schema", "required-non-properties.schema", ], rendererOptions: {},