Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions tools/src/main/js/bundler/test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,51 @@ Minimal snapshot test for `../cli.js`. No test framework required — plain Node
└── snapshot/ # expected bundler output, committed
├── main-bundled.schema.json # pretty bundle, all `$comment` kept
├── main-bundled.min.schema.json # minified bundle, only the root `$comment` kept
└── report.json # bundler report (embedded / external / rewired / warnings)
└── report.json # bundler report (embedded / external / rewired / tree-shake / warnings)
```

The runner copies `fixtures/` to a temporary directory, runs the CLI's `main()` there with
`main.schema.json` as the entry and `modules/` as the modules directory, writes the returned
report to `report.json`, and compares all three generated files byte-for-byte against `snapshot/`.

Each module file name states the edge case it covers (`used-whole`, `used-subdefs-only`,
`used-properties-only`, `unused`, `dynamic-anchor`); `externals/not-bundled` covers a file outside
Each module file name states the edge case it covers; `externals/not-bundled` covers a file outside
the modules directory, the `remote` property in `used-whole` covers an absolute `https://` ref, and
`inArray` in `used-whole` covers refs nested inside an array (`allOf`).

The modules exercise the tree-shaker as follows:

- `used-whole` — referenced as a whole from the entry; the module body survives untouched, but
its unreferenced `$defs/unusedButKept` is dropped nevertheless.
- `used-subdefs-only` — only individual `$defs` are referenced; the module body (`type`, `properties`)
is dropped, the module is hollowed, and `$defs/dropped` is removed. A ref from the dropped body
to `unused` must *not* make `unused` reachable.
- `used-properties-only` — only nested *properties* are referenced, via JSON pointers like
`#/properties/address/properties/geo/properties/lat`. Only the referenced property subtrees
(and what they transitively reach, i.e. `$defs/degrees`) are kept; the containers along the path
(`address`, `geo`, the module itself) are hollowed; unreferenced siblings (`city`, `lon`,
`unusedTopLevel`) and `$defs/unusedDef` are dropped.
- `used-array-items` — refs point *into* schema arrays (`#/properties/combined/allOf/1`,
`#/properties/choice/oneOf/1`) and to a *non-schema* value (`#/properties/status/enum`).
Non-reached array siblings (`allOf/0`, `allOf/2`, `oneOf/0`) must be replaced by a void
`{"not": …}` placeholder — never removed, so indices and pointers stay stable, and never
serialised as `null`. The non-schema target is kept as a whole and its parent `status` is
hollowed. `$defs/keptViaArray` is kept transitively via `allOf/1`; `$defs/droppedDef` is removed.
A ref from the void `allOf/2` to `unused` must *not* make `unused` reachable.
- `unused` — nothing reachable refers to it; the whole module is removed.
- `dynamic-anchor` — declares a `$dynamicAnchor`; the tree-shaker cannot follow anchors and keeps it as is.

The entry schema additionally covers:

- a property *named* `$comment` (`properties.$comment`) — it must survive minification,
only its own `$comment` keyword is stripped;
- local refs into the entry's own `$defs` — they are rewired to the hollow container
`#/$defs/main/$defs/…`, which is marked with `"not": {"$comment": …}`.
`#/$defs/main/$defs/…`, which is marked with `"not": {"$comment": …}`; the unreferenced
`$defs/mainUnused` is dropped from that container.
- an inline object property (`properties.composed`) whose own properties reference
*properties* of a module rather than its `$defs` — via JSON pointers like
`#/properties/address/properties/geo/properties/lat`. Only the referenced property subtrees
(and what they transitively reach) of `used-properties-only` are kept; unreferenced siblings
(`city`, `lon`, `unusedTopLevel`), unreachable `$defs` and `unused.schema.json` are dropped.
*properties* of a module rather than its `$defs` (see `used-properties-only` above).
- refs into *array items* and to a *non-schema value* of a module
(`properties.middle`, `properties.lastChoice`, `properties.statusValues`;
see `used-array-items` above).

Inside the fixtures, the annotation keywords have distinct roles:

Expand All @@ -49,8 +71,9 @@ Inside the fixtures, the annotation keywords have distinct roles:

`report.json` pins the bundler's bookkeeping: which files were embedded and under which
`$defs` key, which refs were left external, every rewired ref (source → target), the
tree-shake result, and the validation warnings. Paths in the report are relative to the
entry schema's directory, so the snapshot is independent of the temporary work directory.
tree-shake result (every `reached`, `hollowed` and `removed` JSON pointer), and the
validation warnings. Paths in the report are relative to the entry schema's directory,
so the snapshot is independent of the temporary work directory.

## Run

Expand Down
18 changes: 18 additions & 0 deletions tools/src/main/js/bundler/test/fixtures/main.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@
"$ref": "modules/used-properties-only.schema.json#/properties/contact"
}
}
},
"middle": {
"$comment": "Ref INTO a schema array (allOf/1) of a module -> siblings allOf/0 and allOf/2 must become void placeholders, not null; the index must stay 1.",
"title": "Middle",
"description": "The middle part of the module's combined schema.",
"$ref": "modules/used-array-items.schema.json#/properties/combined/allOf/1"
},
"lastChoice": {
"$comment": "Ref INTO the last item of a oneOf -> the preceding item must become a void placeholder.",
"title": "Last choice",
"description": "The last alternative of the module's choice.",
"$ref": "modules/used-array-items.schema.json#/properties/choice/oneOf/1"
},
"statusValues": {
"$comment": "Ref to a NON-schema value (the module's enum array). Semantically dubious, but a legal pointer -> the tree-shaker must record it as reached (no crash) and keep the enum intact.",
"title": "Status values",
"description": "Pointer to the module's status enum.",
"$ref": "modules/used-array-items.schema.json#/properties/status/enum"
}
},
"$defs": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "http://example.com/test/modules/used-array-items.schema.json",
"$comment": "FIXTURE: refs point INTO schema arrays and to NON-schema values of this module. The module itself is never referenced as a whole -> hollowed.",
"title": "Used array items",
"description": "A module of which only individual array items and a non-schema value are referenced.",
"type": "object",
"properties": {
"combined": {
"$comment": "Only allOf/1 is referenced from main. allOf/0 and allOf/2 are not reached -> they must become void placeholders, never JSON null, so that the index of allOf/1 stays stable. 'combined' itself is not reached -> hollowed.",
"title": "Combined",
"description": "A composition of which only the middle part is referenced.",
"allOf": [
{
"$comment": "allOf/0: NOT referenced -> void placeholder.",
"title": "Combined part 0",
"description": "The first part.",
"type": "object",
"properties": {
"a": {
"title": "A",
"description": "An unreferenced value.",
"type": "string"
}
}
},
{
"$comment": "allOf/1: referenced from main via #/properties/combined/allOf/1 -> kept as a whole, including its internal ref to $defs/keptViaArray.",
"title": "Combined part 1",
"description": "The middle part.",
"type": "object",
"properties": {
"b": {
"title": "B",
"description": "A value defined in this module.",
"$ref": "#/$defs/keptViaArray"
}
}
},
{
"$comment": "allOf/2: NOT referenced -> void placeholder. Its ref must NOT make unused.schema.json reachable.",
"title": "Combined part 2",
"description": "The last part.",
"$ref": "unused.schema.json"
}
]
},
"choice": {
"$comment": "Only the LAST item of oneOf is referenced -> oneOf/0 becomes a void placeholder, oneOf/1 is kept.",
"title": "Choice",
"description": "A choice of which only the last alternative is referenced.",
"oneOf": [
{
"$comment": "oneOf/0: NOT referenced -> void placeholder.",
"title": "Choice 0",
"description": "The first alternative.",
"type": "null"
},
{
"$comment": "oneOf/1: referenced from main via #/properties/choice/oneOf/1.",
"title": "Choice 1",
"description": "The second alternative.",
"type": "integer"
}
]
},
"status": {
"$comment": "A NON-schema value of this schema (the enum array) is referenced from main via #/properties/status/enum. The enum must be kept as a whole; 'status' itself is not reached -> hollowed; its 'type' is dropped.",
"title": "Status",
"description": "A status value.",
"type": "string",
"enum": [
"on",
"off"
]
}
},
"$defs": {
"keptViaArray": {
"$comment": "Only reachable via combined.allOf/1.",
"title": "Kept via array",
"description": "A boolean value.",
"type": "boolean"
},
"droppedDef": {
"$comment": "Not reachable -> shaken away.",
"title": "Dropped def",
"description": "A definition nothing reachable refers to.",
"type": "null"
}
}
}
Loading
Loading