Skip to content

fix(core): enforce rendererOptions keys in the type system#2939

Merged
schani merged 1 commit into
masterfrom
fix/2933-unknown-renderer-options
Jul 14, 2026
Merged

fix(core): enforce rendererOptions keys in the type system#2939
schani merged 1 commit into
masterfrom
fix/2933-unknown-renderer-options

Conversation

@schani

@schani schani commented Jul 13, 2026

Copy link
Copy Markdown
Member

Fixes #2933.

Unknown rendererOptions keys were silently ignored by the quicktype-core API. Per review, this is now enforced entirely in the type system — no runtime validation.

What this does

  • Makes quicktype(), quicktypeMultiFile(), and quicktypeMultiFileSync() generic over Lang extends LanguageName (inferred from lang), and carries the generic through Options. With a literal lang, rendererOptions is typed as that language's option map, so unknown option names and invalid enum values are compile errors:
await quicktype({
    inputData,
    lang: "csharp",
    rendererOptions: { "totally-bogus": "x" },  // error TS2353: unknown property
});
  • Fixes OptionKey, which fell through to the Option class type for non-enum options — even valid usage like { namespace: "Acme" } failed to typecheck before this. String options are now string, boolean options boolean | "true" | "false" (matching what BooleanOption.getValue accepts), enum options keep their key union.

  • Callers that don't pin lang to a literal — or omit it — keep the old, unchecked behavior, so plain-JS and dynamic callers are unaffected.

Testing

  • test/unit/renderer-options.test-d.ts covers valid options, unknown names, another language's option name, invalid enum values, boolean string forms, and the permissive non-literal fallback, using @ts-expect-error assertions run by vitest's typecheck mode (newly enabled in vitest.config.ts). Verified the harness fails when an expected error doesn't occur.
  • The dead renderer options this work uncovered in test/languages.ts (C++ unions, TypeScript/Flow declare-unions, Zod/Effect array-type, PropTypes runtime-typecheck) stay removed.

🤖 Generated with Claude Code

@schani

schani commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

We shouldn't be validating this at runtime! It should just be a type system thing. If that's not possible, then let's scrap this PR.

@schani

schani commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

It's possible — reworked in f306fa6 as a pure type-system check; the runtime validation is gone.

The pieces were mostly already there: RendererOptions<Lang> is a per-language mapped type, and TypeScript's excess-property checking rejects unknown keys in it. What was missing:

  1. quicktype() never inferred LangOptions dropped the generic that NonInferenceOptions<Lang> already had, so rendererOptions was always typed against the all-languages union, which collapses to an unchecked object. quicktype, quicktypeMultiFile, and quicktypeMultiFileSync are now generic, so a literal lang: "csharp" narrows rendererOptions to C#'s options and {"totally-bogus": "x"} is a compile error — as is an invalid enum value like framework: "GSON".
  2. OptionKey was broken for non-enum options — it fell through to the Option class type itself, so even the valid { namespace: "Acme" } didn't typecheck. Now string options are string and boolean options are boolean | "true" | "false" (matching BooleanOption.getValue).

Callers that don't pin lang to a literal (or omit it) keep the old unchecked behavior, so plain-JS and dynamic callers are unaffected — they just don't get the safety, which is the nature of a type-system-only check.

The type-level behavior is tested with vitest's typecheck mode (*.test-d.ts with @ts-expect-error assertions); I verified the harness fails when an expected error doesn't materialize. The test/languages.ts cleanups stay: those dead options (declare-unions, C++ unions, Zod/Effect array-type, PropTypes runtime-typecheck) were real findings.

🤖 Generated with Claude Code

@schani schani changed the title fix(core): throw on unknown rendererOptions keys fix(core): enforce rendererOptions keys in the type system Jul 13, 2026

schani commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

Review result: no correctness findings in the type-level implementation.

I verified that the PR builds cleanly and that all 78 unit tests plus the new typechecking suite pass. The GitHub Test workflow is also successful.

One non-blocking cleanup: DriverNoSamplesForTopLevel remains in Messages.ts but has no callers, apparently left over from the removed runtime-validation implementation. It can be deleted before merge, but I would not block the PR on it.

quicktype(), quicktypeMultiFile(), and quicktypeMultiFileSync() are now
generic over the language name, so passing a literal `lang` narrows
`rendererOptions` to that language's option map: unknown option names
and invalid enum values are compile errors.  Callers that don't pin
`lang` to a literal keep the old, unchecked behavior, and there is no
runtime validation.

This also fixes OptionKey, which fell through to the Option class type
for non-enum options, making even valid string-option values like
`{ namespace: "Acme" }` fail to typecheck.  Boolean options accept
booleans and their string forms, which is what BooleanOption.getValue
implements.

The type-level behavior is tested with vitest's typecheck mode via
@ts-expect-error assertions in renderer-options.test-d.ts.

The dead renderer options this uncovered in test/languages.ts
(C++ `unions`, TypeScript/Flow `declare-unions`, Zod/Effect
`array-type`, PropTypes `runtime-typecheck`) stay removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@schani
schani force-pushed the fix/2933-unknown-renderer-options branch from f306fa6 to e1a1404 Compare July 14, 2026 12:54
@schani

schani commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

Good catch on the symptom, but the message isn't actually dead — DriverNoSamplesForTopLevel belongs to the merged empty-samples fix (#2937, commit ea22d3d), which calls it twice in Inputs.ts. This branch was just based on a pre-merge master, and my revert of Messages.ts restored the file from post-merge master, so the branch showed the message without its caller.

Fixed properly in e1a1404 by rebasing onto current master (and squashing out the discarded runtime-validation commit): the branch no longer touches Messages.ts at all, and the message has its callers. All unit + type tests pass.

🤖 Generated with Claude Code

@schani
schani merged commit caec102 into master Jul 14, 2026
24 checks passed
@schani
schani deleted the fix/2933-unknown-renderer-options branch July 14, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: quicktype-core silently ignores unknown rendererOptions keys

1 participant