Skip to content

Declare a boolean root schema (true/false) as unknown/never instead of emitting an empty file — lands after #884 - #887

Draft
claude[bot] wants to merge 5 commits into
masterfrom
claude/hearth-thread-ejgcib
Draft

claude[bot] wants to merge 5 commits into
masterfrom
claude/hearth-thread-ejgcib

Conversation

@claude

@claude claude Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Draft until #884 lands — nothing for you to do. This branch carries #884's head merged in (one import line in src/parser.ts conflicted; both branches touch that file), so merging this before #884 would land #884's change with it.

Output change, for boolean root schemas only: a schema file that is nothing but true or false now compiles to a named type instead of an empty file. Every other schema's output is byte-identical (checked below).

What changes for you

From draft 6 on, a JSON Schema document may be a bare boolean: true accepts every value, false accepts none. This compiler emitted nothing but the banner comment for such a file, because the root had no keyword to be named by and so was never declared. With unreachableDefinitions on, or through compileFiles, it crashed (Cannot read properties of undefined (reading 'has')).

Now the root is declared under the file's name, the same rule an object root without an $id already follows, and compile() accepts a boolean:

Input Before After
Anything.json containing true banner comment only export type Anything = unknown;
Nothing.json containing false banner comment only export type Nothing = never;
compile(true, 'Root') banner comment only export type Root = unknown;
compile(false, 'Root') banner comment only export type Root = never;
echo true | json2ts (stdin) banner comment only export type NoName = unknown;
compile(true, 'Root', {unreachableDefinitions: true}), compileFiles with a boolean member crash the type above

The library, the CLI (file, stdin, YAML, directory, --imports), compileFromFile and compileFiles all go through the same path. --no-unknownAny gives any, as for every other true schema. The customName option is not consulted for a boolean root, since that function is handed schema objects; the file name is the name.

How

  • parseRoot (src/parser.ts): a boolean root gets parseBooleanSchema's node plus a standalone name; an object root goes to parse as before. parseUnreachableDefinitions returns nothing for a boolean root instead of reading its (absent) type set.
  • rootNameFromFile (src/utils.ts): the one rule for a root without a name of its own (toSafeString(justName(file)) || 'NoName'), now shared by the normalizer's $id rule and the parser.
  • compile()'s schema parameter accepts boolean. Nothing else in the public API changes.
  • README: the draft 6 boolean-schema row no longer says a root true/false errors (and no longer says a $ref to one crashes, which Fix crash on $ref to a boolean schema or other non-object value (#725) #809 fixed).

Tests

Conformance baseline

bun run conformance on master reports the eight boolean_schema.json groups (drafts 6, 7, 2019-09, 2020-12; true and false each) as invalid-output (no Root declaration). They now score perfectly, so test/conformance/baseline.json is re-recorded (node test/conformance/run.js --update): the eight group lines change from "invalid-output" to [0,9,0,0] (true: 0 of 9 valid instances rejected) and [0,0,0,9] (false: 0 of 9 invalid instances accepted), and the totals move accordingly (invalid-output 8 → 0, valid instances 5262 → 5298, invalid instances 3606 → 3642). No other line moves; the harness exits 0 on this head.

Merge check

git merge-tree --write-tree of fef9931 against each open draft: clean against #879, #880, #881, #882, #883, #885, #886; conflicts with #884 on the import … from './utils' line of src/parser.ts (#884 adds hasType, this adds rootNameFromFile). Resolved by merging #884's head 9488e4f into this branch (7ff419a, import line takes both names, nothing else hand-edited): snapshot interdiff against master is exactly this PR's two entries plus #884's own diff, suite and conformance green. So this lands after #884; once #884 is on master the merge commit is a no-op.

Changelog

  • A boolean root schema (true / false) compiles to export type X = unknown / never instead of an empty file (and no longer crashes with unreachableDefinitions or compileFiles).

…leaking the `KeyNameFromDefinitionItems` placeholder

The array branch of the parser handed its items the literal key
`{keyNameFromDefinition}Items` (a template literal without `${}`), and a
titleless `enum` with `tsEnumNames` among the items took that as its
declared name. Items now get the array's name, else its property key,
plus `Items`; the copy of a nullable array (`type: ['array', 'null']`)
inherits the same. Where the array has neither, an `anyOf` branch say,
the enum degrades to a union of literals like any other unnamed enum.
Drops the filter that skipped the placeholder when naming anonymous
recursive types.
…s after the schema above

An array that is a oneOf/anyOf branch, an allOf member, a tuple slot or
additionalItems has no key of its own, so its items had none either and a
titleless tsEnumNames enum among them fell back to a union of literals
(under inferStringEnumKeysFromValues the Azure deployment template lost
265 enum declarations). Such an array now takes the key or name of the
schema above it, so its items are named `<Key>Items` like a plain
array's; a bare enum in those positions still stays a union of literals.
Also drops the CHANGELOG hunk: the entry goes in the pull request body.
An array inside a oneOf within an anyOf, an anyOf within an allOf, or an
OpenAPI `nullable: true` array that is itself a oneOf branch (the
normalizer nests an anyOf inside that oneOf) sat two keyless levels down,
so it still got no key and its titleless tsEnumNames enum fell back to a
union of literals. A set operation in a keyless position now passes the
key on to its members, unless it carries an enum of its own, which would
otherwise be declared under the property's name.
…nstead of emitting an empty file

A document that is nothing but `true` or `false` (a boolean schema, draft 6+) compiled to the banner comment alone, since the root had no keyword to be named by and so was never declared; with `unreachableDefinitions` on, or through `compileFiles`, it crashed. The root is now declared under the file's name, as an object root without an `$id` already is: `export type File = unknown;` for `true`, `= never;` for `false`. `compile()` accepts a boolean. The eight boolean_schema.json groups of the JSON-Schema-Test-Suite move from "invalid-output" to fully supported in the conformance baseline.
@claude claude Bot changed the title Declare a boolean root schema (true/false) as unknown/never instead of emitting an empty file Declare a boolean root schema (true/false) as unknown/never instead of emitting an empty file — lands after #884 Sep 7, 2026
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.

1 participant