From 78b64823afd13666466d9d070f3a31337e03eaed Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 26 Oct 2026 13:01:03 +0100 Subject: [PATCH 1/3] chore/bundler-rewire-custom-paths Signed-off-by: Jan Kowalleck --- tools/src/main/js/bundler/test/README.md | 6 +++ .../js/bundler/test/fixtures/main.schema.json | 6 +++ .../modules/used-custom-defs.schema.json | 52 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 tools/src/main/js/bundler/test/fixtures/modules/used-custom-defs.schema.json diff --git a/tools/src/main/js/bundler/test/README.md b/tools/src/main/js/bundler/test/README.md index d693bd59..b2f30851 100644 --- a/tools/src/main/js/bundler/test/README.md +++ b/tools/src/main/js/bundler/test/README.md @@ -52,6 +52,10 @@ The modules exercise the tree-shaker as follows: items *after* the last reached one (`allOf/4`) may be truncated. The unreferenced `examples` of `choice` are removed. `$defs/keptViaArray` is kept transitively via `allOf/1`; `$defs/droppedDef` and the nested `oneOf/*/$defs/droppedDef` are removed. +- `used-custom-defs` — definitions live in non-standard containers (`custom-defs`, + `even-more-custom-defs`) instead of `$defs`. The entry refs `#/custom-defs/customKept`, which refs + `#/even-more-custom-defs/deepKept`. Both containers must be traversed; `customKept` and `deepKept` + are kept, `customDropped`/`deepDropped` and the module body are dropped, the containers are hollowed. - `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. @@ -66,6 +70,8 @@ The entry schema additionally covers: *properties* of a module rather than its `$defs` (see `used-properties-only` above). - refs into *array items* of a module (`properties.middle`, `properties.lastChoice`; see `used-array-items` above). +- a ref into a *non-standard* definitions container of a module (`properties.customDef`; + see `used-custom-defs` above). - refs to an empty `{}`, a `$comment`-only and an annotation-only schema (`properties.anyValue`, `properties.todoValue`, `properties.annotatedValue`) — reached, so they must survive and must *not* be hollowed. diff --git a/tools/src/main/js/bundler/test/fixtures/main.schema.json b/tools/src/main/js/bundler/test/fixtures/main.schema.json index 3e10d406..3d6f6cc3 100644 --- a/tools/src/main/js/bundler/test/fixtures/main.schema.json +++ b/tools/src/main/js/bundler/test/fixtures/main.schema.json @@ -120,6 +120,12 @@ "title": "Annotated value", "description": "An annotation-only schema.", "$ref": "modules/used-subdefs-only.schema.json#/$defs/keptAnnotationsOnly" + }, + "customDef": { + "$comment": "Ref into a NON-STANDARD definitions container (`custom-defs`, not `$defs`) of a module, which itself refs into yet another non-standard container (`even-more-custom-defs`). Both must be followed; unreferenced siblings in both containers and the module body are dropped.", + "title": "Custom def", + "description": "A value defined in a non-standard definitions container of another module.", + "$ref": "modules/used-custom-defs.schema.json#/custom-defs/customKept" } }, "$defs": { diff --git a/tools/src/main/js/bundler/test/fixtures/modules/used-custom-defs.schema.json b/tools/src/main/js/bundler/test/fixtures/modules/used-custom-defs.schema.json new file mode 100644 index 00000000..6839d83c --- /dev/null +++ b/tools/src/main/js/bundler/test/fixtures/modules/used-custom-defs.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/test/modules/used-custom-defs.schema.json", + "$comment": "FIXTURE: definitions live in NON-STANDARD containers (`custom-defs`, `even-more-custom-defs`) instead of `$defs`. The entry refs into `custom-defs`, which in turn refs into `even-more-custom-defs`. Both containers must be traversed by the ref-rewriter and the tree-shaker; the reached defs are kept, the unreferenced siblings are dropped, the containers are hollowed, and the body of this module is dropped.", + "title": "Used custom defs", + "description": "A module whose definitions live in non-standard containers.", + "type": "object", + "properties": { + "body": { + "$comment": "Lives in the dropped body -> this ref must NOT make `custom-defs/customDropped` reachable.", + "title": "Body", + "description": "The body of this module.", + "$ref": "#/custom-defs/customDropped" + } + }, + "custom-defs": { + "customKept": { + "$comment": "Reached from main via a JSON pointer into a non-`$defs` container. Transitively keeps `even-more-custom-defs/deepKept`.", + "title": "Custom kept", + "description": "A definition in a non-standard container, reached from the root schema.", + "type": "object", + "properties": { + "deep": { + "$comment": "Internal ref into ANOTHER non-standard container -> must be rewired and keep `deepKept`.", + "title": "Deep", + "description": "A value defined in an even more custom container.", + "$ref": "#/even-more-custom-defs/deepKept" + } + } + }, + "customDropped": { + "$comment": "Only referenced from the dropped body -> shaken away.", + "title": "Custom dropped", + "description": "A definition nothing reachable refers to.", + "type": "null" + } + }, + "even-more-custom-defs": { + "deepKept": { + "$comment": "Only reachable via `custom-defs/customKept`.", + "title": "Deep kept", + "description": "A string value.", + "type": "string" + }, + "deepDropped": { + "$comment": "Not reachable -> shaken away.", + "title": "Deep dropped", + "description": "A definition nothing reachable refers to.", + "type": "null" + } + } +} From 5771c8149c1109d2fd266cf2f9920812846c0501 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 26 Oct 2026 14:13:14 +0100 Subject: [PATCH 2/3] chore(bundler): rewire unusualpaths Signed-off-by: Jan Kowalleck --- tools/src/main/js/bundler/src/bundle.js | 58 +++++++++++++------ .../snapshot/main-bundled.min.schema.json | 2 +- .../test/snapshot/main-bundled.schema.json | 55 ++++++++++++++++++ .../main/js/bundler/test/snapshot/report.json | 16 +++++ 4 files changed, 112 insertions(+), 19 deletions(-) diff --git a/tools/src/main/js/bundler/src/bundle.js b/tools/src/main/js/bundler/src/bundle.js index d9366f12..e2b29f4e 100644 --- a/tools/src/main/js/bundler/src/bundle.js +++ b/tools/src/main/js/bundler/src/bundle.js @@ -1,9 +1,14 @@ import {basename, dirname, relative, resolve} from 'node:path'; import {ok} from 'node:assert/strict'; -import {JSON_SCHEMA_RE, getJsonfile, posixPath, findDuplicateValues} from './helpers/common.js'; import { - escapeJsonPointer, isJsonPointer, + posixPath, + JSON_SCHEMA_RE, getJsonfile, + objectGetOwnPath, PATH_NOT_FOUND, + findDuplicateValues +} from './helpers/common.js'; +import { + escapeJsonPointer, isJsonPointer, jsonPointer2stack, PREFIX as JP_PREFIX, ROOT_POINTER } from './helpers/json-pointer-spec.js'; import { @@ -86,16 +91,22 @@ class ModuleNameCollisionError extends Error { * @param {Iterable} includeFiles Absolute path to schema files that shall be bundled. * @param {string} targetFile Absolute path to target schema file. * @returns {Promise} + * @throws {RangeError} on a local ref whose fragment is not a JSON Pointer (e.g. an anchor */ export const bundle = async (entryFile, includeFiles, targetFile) => { const entryDir = dirname(entryFile); const targetDir = dirname(targetFile); - /** @type {ReadonlyMap} */ - const fileModuleNames = Object.freeze(new Map( - [entryFile, ...includeFiles].map(f => [f, makeModuleName(basename(f))]) - )); - const _dupModuleNames = findDuplicateValues(fileModuleNames) + /** @type {ReadonlyMap>} */ + const fileModuleSchema = Object.freeze(new Map(await Promise.all( + [entryFile, ...includeFiles].map(async f => { + const module = makeModuleName(basename(f)); + const schema = await getJsonfile(f); + ok(isSchemaObject(schema), `Not schema: ${f}`); + return [f, Object.freeze({module, schema})]; + })))); + const _dupModuleNames = findDuplicateValues(new Map( + fileModuleSchema.entries().map(([f, {module}]) => [f, module]))); if (_dupModuleNames.size) { throw new ModuleNameCollisionError(_dupModuleNames); } @@ -118,6 +129,8 @@ export const bundle = async (entryFile, includeFiles, targetFile) => { */ const rewiredMap = new Map(); + /** @type {WeakSet} */ + const _rewireRefsSeen = new WeakSet(); /** * Rewire refs in place. * @@ -128,6 +141,8 @@ export const bundle = async (entryFile, includeFiles, targetFile) => { * @returns {void} */ const rewireRefs = (schema, sourceFile) => { + if (_rewireRefsSeen.has(schema)) return; + _rewireRefsSeen.add(schema); const sourceDir = dirname(sourceFile); for (const k of REF_KEYWORDS) { const v = schema[k]; @@ -142,8 +157,8 @@ export const bundle = async (entryFile, includeFiles, targetFile) => { const refFile = path ? resolve(sourceDir, ...path.split(URI_PATH_SEP)) : sourceFile; - const moduleName = fileModuleNames.get(refFile); - if (moduleName) { + const refMS = fileModuleSchema.get(refFile) + if (refMS) { if (frag !== null && !isJsonPointer(frag)) { // currently dont support anchors - only JSON pointer. throw new RangeError(`Unsupported ref fragment: ${v}`); @@ -152,7 +167,7 @@ export const bundle = async (entryFile, includeFiles, targetFile) => { path: null, frag: (refFile === entryFile && !frag?.startsWith(JP_DEFS_PREFIX) ? ROOT_POINTER - : `${JP_DEFS_PREFIX}${escapeJsonPointer(moduleName)}` + : `${JP_DEFS_PREFIX}${escapeJsonPointer(refMS.module)}` ) + (frag ?? '') }; } else { @@ -166,25 +181,33 @@ export const bundle = async (entryFile, includeFiles, targetFile) => { rewiredMap.set(refJoin({path: refFile, frag}), rewired); if (rewiredRS.path) { externals.add(rewired); + } else { + const tSchema = objectGetOwnPath(refMS.schema, jsonPointer2stack(frag ?? ROOT_POINTER)); + if (tSchema === PATH_NOT_FOUND) { + throw RangeError(`Missing target for ref: ${v}`); + } + if (isSchemaObject(tSchema)) { + rewireRefs(tSchema, refFile); + } else if (typeof tSchema !== 'boolean') { + throw new RangeError(`Ref target is not schema nor boolean: ${v}`) + } } } subschemas(schema).forEach(([s]) => rewireRefs(s, sourceFile)); - } + }; + fileModuleSchema.forEach(({schema}, sourceFile) => rewireRefs(schema, sourceFile)); - const schema = await getJsonfile(entryFile); - ok(isSchemaObject(schema), `Not schema: ${entryFile}`); + const {schema, module: entryModuleName} = fileModuleSchema.get(entryFile); if (URL.canParse(schema[ID_KEYWORD])) { schema[ID_KEYWORD] = new URL( posixPath(relative(entryDir, targetFile)), schema[ID_KEYWORD] ).toString(); } - rewireRefs(schema, entryFile); const schemaDefsOrig = schema[DEFS_KEYWORD]; schema[DEFS_KEYWORD] = Object.fromEntries( // sort for deterministic order - [...fileModuleNames.values()].sort().map(m => [m, undefined])); + [...fileModuleSchema.values().map(({module}) => module)].sort().map(m => [m, undefined])); - const entryModuleName = fileModuleNames.get(entryFile); if (schemaDefsOrig) { schema[DEFS_KEYWORD][entryModuleName] = { [DEFS_KEYWORD]: schemaDefsOrig, @@ -198,9 +221,8 @@ export const bundle = async (entryFile, includeFiles, targetFile) => { delete schema[DEFS_KEYWORD][entryModuleName]; } - for (const [includeFile, moduleName] of fileModuleNames) { + for (const [includeFile, {module: moduleName, schema: includeSchema}] of fileModuleSchema) { if (includeFile === entryFile) continue; - const includeSchema = await getJsonfile(includeFile); ok(isSchemaObject(includeSchema), `Not schema: ${includeFile}`); delete includeSchema[SCHEMA_KEYWORD]; delete includeSchema[ID_KEYWORD]; diff --git a/tools/src/main/js/bundler/test/snapshot/main-bundled.min.schema.json b/tools/src/main/js/bundler/test/snapshot/main-bundled.min.schema.json index cd6514af..2a09dc7c 100644 --- a/tools/src/main/js/bundler/test/snapshot/main-bundled.min.schema.json +++ b/tools/src/main/js/bundler/test/snapshot/main-bundled.min.schema.json @@ -1 +1 @@ -{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"http://example.com/test/main-bundled.min.schema.json","$comment":"FIXTURE: root schema; entry point of the bundle. This $comment is the only one that survives minification. The root title/description must survive in both outputs. The root `required`/`examples` are data arrays inside the (reached) root schema and must survive the tree-shaker untouched.","title":"Main","description":"Root document of the bundler test fixtures.","type":"object","required":["whole"],"examples":[{"whole":{}}],"properties":{"$comment":{"type":"string","title":"Comment"},"whole":{"title":"Whole","description":"A module referenced in its entirety.","$ref":"#/$defs/used-whole"},"wholeData":{"title":"Whole data","description":"A data-heavy module referenced in its entirety.","$ref":"#/$defs/used-whole-data"},"sub":{"title":"Sub","description":"A single definition of another module.","$ref":"#/$defs/used-subdefs-only/$defs/kept"},"local":{"title":"Local","description":"A definition of this very schema.","$ref":"#/$defs/main/$defs/mainLocal"},"composed":{"title":"Composed","description":"An object assembled from parts of another module.","type":"object","$defs":{"rootNestedDropped":{"title":"Root nested dropped","description":"An unreferenced nested definition of the root.","type":"null"}},"properties":{"street":{"title":"Street","description":"The street, taken from the module's address.","$ref":"#/$defs/used-properties-only/properties/address/properties/street"},"latitude":{"title":"Latitude","description":"The latitude, taken from the module's address.geo.","$ref":"#/$defs/used-properties-only/properties/address/properties/geo/properties/lat"},"contact":{"title":"Contact","description":"The contact block of the module, as is.","$ref":"#/$defs/used-properties-only/properties/contact"},"nested":{"title":"Nested","description":"A value defined in a property-level $defs.","$ref":"#/$defs/used-whole/properties/withDefs/$defs/nestedKept"}}},"middle":{"title":"Middle","description":"The middle part of the module's combined schema.","anyOf":[{"$ref":"#/$defs/used-array-items/properties/combined/allOf/1"},{"$ref":"#/$defs/used-array-items/properties/combined/allOf/3"}]},"lastChoice":{"title":"Last choice","description":"The last alternative of the module's choice.","$ref":"#/$defs/used-array-items/properties/choice/oneOf/1"},"anyValue":{"title":"Any value","description":"Accepts anything.","$ref":"#/$defs/used-subdefs-only/$defs/keptEmpty"},"todoValue":{"title":"Todo value","description":"A placeholder.","$ref":"#/$defs/used-subdefs-only/$defs/keptCommentOnly"},"annotatedValue":{"title":"Annotated value","description":"An annotation-only schema.","$ref":"#/$defs/used-subdefs-only/$defs/keptAnnotationsOnly"}},"$defs":{"dynamic-anchor":{"$dynamicAnchor":"node","title":"Dynamic anchor","description":"A recursive tree node, addressable via a dynamic anchor.","type":"object","properties":{"children":{"title":"Children","description":"The child nodes of this node.","type":"array","items":{"title":"Child","description":"A nested node.","$ref":"#/$defs/dynamic-anchor"}}}},"main":{"$defs":{"mainLocal":{"title":"Main local","description":"A local string definition of the root schema.","type":"string"},"mainUnused":{"title":"Main unused","description":"A definition nothing refers to.","type":"null"}},"not":{}},"unused":{"title":"Unused","description":"A module that nothing reachable refers to.","type":"object","properties":{"pullsNothingIn":{"title":"Pulls nothing in","description":"A flag defined by another module.","$ref":"#/$defs/used-whole/$defs/unused"}}},"used-array-items":{"title":"Used array items","description":"A module of which only individual array items and a non-schema value are referenced.","type":"object","properties":{"combined":{"title":"Combined","description":"A composition of which only the middle part is referenced.","allOf":[{"title":"Combined part 0","description":"The first part.","type":"object","properties":{"a":{"title":"A","description":"An unreferenced value.","type":"string"}}},{"title":"Combined part 1","description":"The middle part.","type":"object","properties":{"b":{"title":"B","description":"A value defined in this module.","$ref":"#/$defs/used-array-items/$defs/keptViaArray"}}},false,true,{"title":"Combined part 6","description":"The end part.","type":"object","properties":{"a":{"title":"A","description":"An unreferenced value.","type":"string"}}},{"$ref":"#/$defs/used-array-items/$defs/droppedDef"}]},"choice":{"title":"Choice","description":"A choice of which only the last alternative is referenced.","oneOf":[{"title":"Choice 0","description":"The first alternative.","type":"null","$defs":{"droppedDef":{"title":"Dropped def","description":"A definition nothing reachable refers to.","type":"null"}}},{"title":"Choice 1","description":"The second alternative.","type":"integer","$defs":{"droppedDef":{"title":"Dropped def","description":"A definition nothing reachable refers to.","type":"null"}}}],"examples":[null,1,2,3]}},"$defs":{"keptViaArray":{"title":"Kept via array","description":"A boolean value.","type":"boolean"},"droppedDef":{"title":"Dropped def","description":"A definition nothing reachable refers to.","type":"null"}}},"used-properties-only":{"title":"Used properties only","description":"A module of which only nested properties are referenced.","type":"object","properties":{"address":{"title":"Address","description":"A postal address.","type":"object","properties":{"street":{"title":"Street","description":"Street name and number.","type":"string"},"city":{"title":"City","description":"Name of the city.","type":"string"},"geo":{"title":"Geo","description":"Geographic coordinates.","type":"object","properties":{"lat":{"title":"Latitude","description":"Latitude in degrees.","$ref":"#/$defs/used-properties-only/$defs/degrees"},"lon":{"title":"Longitude","description":"Longitude in degrees.","$ref":"#/$defs/used-properties-only/$defs/degrees"}}}}},"contact":{"title":"Contact","description":"Contact details.","type":"object","properties":{"email":{"title":"E-mail","description":"An e-mail address.","type":"string","format":"email"},"phone":{"title":"Phone","description":"A phone number.","type":"string"}}},"unusedTopLevel":{"title":"Unused top level","description":"A property nothing refers to.","$ref":"#/$defs/unused"}},"$defs":{"degrees":{"title":"Degrees","description":"An angle in degrees.","type":"number","minimum":-180,"maximum":180},"unusedDef":{"title":"Unused def","description":"A definition nothing reachable refers to.","type":"null"}}},"used-subdefs-only":{"title":"Used sub-defs only","description":"A module of which only individual definitions are referenced.","type":"object","properties":{"body":{"title":"Body","description":"The body of this module.","$ref":"#/$defs/unused"}},"$defs":{"kept":{"title":"Kept","description":"A definition that is reached directly from the root schema.","type":"object","properties":{"shared":{"title":"Shared","description":"A value defined by another module.","$ref":"#/$defs/used-whole/$defs/sharedByOther"},"fromMain":{"title":"From main","description":"A value defined by the root schema.","$ref":"#/$defs/main/$defs/mainLocal"},"chain":{"title":"Chain","description":"A value defined further down in this module.","$ref":"#/$defs/used-subdefs-only/$defs/keptTransitively"}}},"keptEmpty":{},"keptCommentOnly":{},"keptAnnotationsOnly":{"title":"Kept annotations only","description":"A schema that is just an annotation."},"keptTransitively":{"title":"Kept transitively","description":"A string value.","type":"string"},"dropped":{"title":"Dropped","description":"A definition nothing reachable refers to.","type":"null"}}},"used-whole":{"title":"Used whole","description":"A module that is referenced in its entirety.","type":"object","properties":{"name":{"title":"Name","description":"A display name.","type":"string","$defs":{"nestedDropped":{"title":"Nested dropped","description":"An unreferenced nested definition.","type":"null"}}},"withDefs":{"title":"With defs","description":"A property that hosts its own definitions.","anyOf":[{"title":"Is string","type":"string"},{"title":"Non-zero number","type":"number","if":{"const":0,"$defs":{"unused":{"title":"Unused","type":"null"}}},"then":false,"else":true,"$defs":{"unusedPositive":{"title":"Number > 0","type":"number","exclusiveMinimum":0},"unusedNegative":{"title":"Number < 0","type":"number","exclusiveMaximum":0}}}],"$defs":{"nestedKept":{"title":"Nested kept","description":"A referenced nested definition.","type":"integer"},"nestedDropped":{"title":"Nested dropped","description":"An unreferenced nested definition.","type":"null"}}},"inner":{"title":"Inner","description":"Sibling annotations next to a $ref: title/description are kept, while the sibling $comment is removed in the minified output.","$ref":"#/$defs/used-whole/$defs/inner"},"license":{"title":"License","description":"The license of this item.","$ref":"externals/not-bundled.schema.json#/$defs/licenseId"},"remote":{"title":"Remote","description":"A thing defined by a remote schema.","$ref":"https://example.com/remote.schema.json#/$defs/thing"},"inArray":{"title":"In array","description":"A composition of several referenced schemas.","allOf":[{"$ref":"#/$defs/used-whole/$defs/inner"},{"$ref":"#/$defs/used-subdefs-only/$defs/keptTransitively"},{"$ref":"externals/not-bundled.schema.json#/$defs/licenseId"}]}},"$defs":{"inner":{"title":"Inner","description":"An integer value.","type":"integer"},"unused":{"title":"Unused","description":"A flag.","type":"boolean"},"sharedByOther":{"title":"Shared by other","description":"A numeric value shared across modules.","type":"number"}}},"used-whole-data":{"title":"Used whole data","description":"A module referenced in its entirety that carries data-bearing keywords inside reached schemas.","type":"object","required":["status","level"],"examples":[{"status":"on","level":1},{"status":"off","level":2}],"default":{"status":"on","level":1},"properties":{"status":{"title":"Status","description":"A status value.","type":"string","enum":["on","off"],"meta:enum":{"on":"It is on.","off":"It is off."}},"level":{"title":"Level","description":"A fixed level.","type":"integer","const":1,"examples":[1],"default":1},"flags":{"title":"Flags","description":"An object with boolean-schema properties.","type":"object","required":["anything"],"properties":{"anything":true,"nothing":false},"additionalProperties":false},"tags":{"title":"Tags","description":"A list of tags.","type":"array","uniqueItems":true,"minItems":0,"items":{"title":"Tag","description":"A single tag.","type":"string"}}},"$defs":{"unusedDef":{"title":"Unused def","description":"A definition nothing reachable refers to.","type":"null"}}}}} \ No newline at end of file +{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"http://example.com/test/main-bundled.min.schema.json","$comment":"FIXTURE: root schema; entry point of the bundle. This $comment is the only one that survives minification. The root title/description must survive in both outputs. The root `required`/`examples` are data arrays inside the (reached) root schema and must survive the tree-shaker untouched.","title":"Main","description":"Root document of the bundler test fixtures.","type":"object","required":["whole"],"examples":[{"whole":{}}],"properties":{"$comment":{"type":"string","title":"Comment"},"whole":{"title":"Whole","description":"A module referenced in its entirety.","$ref":"#/$defs/used-whole"},"wholeData":{"title":"Whole data","description":"A data-heavy module referenced in its entirety.","$ref":"#/$defs/used-whole-data"},"sub":{"title":"Sub","description":"A single definition of another module.","$ref":"#/$defs/used-subdefs-only/$defs/kept"},"local":{"title":"Local","description":"A definition of this very schema.","$ref":"#/$defs/main/$defs/mainLocal"},"composed":{"title":"Composed","description":"An object assembled from parts of another module.","type":"object","$defs":{"rootNestedDropped":{"title":"Root nested dropped","description":"An unreferenced nested definition of the root.","type":"null"}},"properties":{"street":{"title":"Street","description":"The street, taken from the module's address.","$ref":"#/$defs/used-properties-only/properties/address/properties/street"},"latitude":{"title":"Latitude","description":"The latitude, taken from the module's address.geo.","$ref":"#/$defs/used-properties-only/properties/address/properties/geo/properties/lat"},"contact":{"title":"Contact","description":"The contact block of the module, as is.","$ref":"#/$defs/used-properties-only/properties/contact"},"nested":{"title":"Nested","description":"A value defined in a property-level $defs.","$ref":"#/$defs/used-whole/properties/withDefs/$defs/nestedKept"}}},"middle":{"title":"Middle","description":"The middle part of the module's combined schema.","anyOf":[{"$ref":"#/$defs/used-array-items/properties/combined/allOf/1"},{"$ref":"#/$defs/used-array-items/properties/combined/allOf/3"}]},"lastChoice":{"title":"Last choice","description":"The last alternative of the module's choice.","$ref":"#/$defs/used-array-items/properties/choice/oneOf/1"},"anyValue":{"title":"Any value","description":"Accepts anything.","$ref":"#/$defs/used-subdefs-only/$defs/keptEmpty"},"todoValue":{"title":"Todo value","description":"A placeholder.","$ref":"#/$defs/used-subdefs-only/$defs/keptCommentOnly"},"annotatedValue":{"title":"Annotated value","description":"An annotation-only schema.","$ref":"#/$defs/used-subdefs-only/$defs/keptAnnotationsOnly"},"customDef":{"title":"Custom def","description":"A value defined in a non-standard definitions container of another module.","$ref":"#/$defs/used-custom-defs/custom-defs/customKept"}},"$defs":{"dynamic-anchor":{"$dynamicAnchor":"node","title":"Dynamic anchor","description":"A recursive tree node, addressable via a dynamic anchor.","type":"object","properties":{"children":{"title":"Children","description":"The child nodes of this node.","type":"array","items":{"title":"Child","description":"A nested node.","$ref":"#/$defs/dynamic-anchor"}}}},"main":{"$defs":{"mainLocal":{"title":"Main local","description":"A local string definition of the root schema.","type":"string"},"mainUnused":{"title":"Main unused","description":"A definition nothing refers to.","type":"null"}},"not":{}},"unused":{"title":"Unused","description":"A module that nothing reachable refers to.","type":"object","properties":{"pullsNothingIn":{"title":"Pulls nothing in","description":"A flag defined by another module.","$ref":"#/$defs/used-whole/$defs/unused"}}},"used-array-items":{"title":"Used array items","description":"A module of which only individual array items and a non-schema value are referenced.","type":"object","properties":{"combined":{"title":"Combined","description":"A composition of which only the middle part is referenced.","allOf":[{"title":"Combined part 0","description":"The first part.","type":"object","properties":{"a":{"title":"A","description":"An unreferenced value.","type":"string"}}},{"title":"Combined part 1","description":"The middle part.","type":"object","properties":{"b":{"title":"B","description":"A value defined in this module.","$ref":"#/$defs/used-array-items/$defs/keptViaArray"}}},false,true,{"title":"Combined part 6","description":"The end part.","type":"object","properties":{"a":{"title":"A","description":"An unreferenced value.","type":"string"}}},{"$ref":"#/$defs/used-array-items/$defs/droppedDef"}]},"choice":{"title":"Choice","description":"A choice of which only the last alternative is referenced.","oneOf":[{"title":"Choice 0","description":"The first alternative.","type":"null","$defs":{"droppedDef":{"title":"Dropped def","description":"A definition nothing reachable refers to.","type":"null"}}},{"title":"Choice 1","description":"The second alternative.","type":"integer","$defs":{"droppedDef":{"title":"Dropped def","description":"A definition nothing reachable refers to.","type":"null"}}}],"examples":[null,1,2,3]}},"$defs":{"keptViaArray":{"title":"Kept via array","description":"A boolean value.","type":"boolean"},"droppedDef":{"title":"Dropped def","description":"A definition nothing reachable refers to.","type":"null"}}},"used-custom-defs":{"title":"Used custom defs","description":"A module whose definitions live in non-standard containers.","type":"object","properties":{"body":{"title":"Body","description":"The body of this module.","$ref":"#/$defs/used-custom-defs/custom-defs/customDropped"}},"custom-defs":{"customKept":{"$comment":"Reached from main via a JSON pointer into a non-`$defs` container. Transitively keeps `even-more-custom-defs/deepKept`.","title":"Custom kept","description":"A definition in a non-standard container, reached from the root schema.","type":"object","properties":{"deep":{"$comment":"Internal ref into ANOTHER non-standard container -> must be rewired and keep `deepKept`.","title":"Deep","description":"A value defined in an even more custom container.","$ref":"#/$defs/used-custom-defs/even-more-custom-defs/deepKept"}}},"customDropped":{"$comment":"Only referenced from the dropped body -> shaken away.","title":"Custom dropped","description":"A definition nothing reachable refers to.","type":"null"}},"even-more-custom-defs":{"deepKept":{"$comment":"Only reachable via `custom-defs/customKept`.","title":"Deep kept","description":"A string value.","type":"string"},"deepDropped":{"$comment":"Not reachable -> shaken away.","title":"Deep dropped","description":"A definition nothing reachable refers to.","type":"null"}}},"used-properties-only":{"title":"Used properties only","description":"A module of which only nested properties are referenced.","type":"object","properties":{"address":{"title":"Address","description":"A postal address.","type":"object","properties":{"street":{"title":"Street","description":"Street name and number.","type":"string"},"city":{"title":"City","description":"Name of the city.","type":"string"},"geo":{"title":"Geo","description":"Geographic coordinates.","type":"object","properties":{"lat":{"title":"Latitude","description":"Latitude in degrees.","$ref":"#/$defs/used-properties-only/$defs/degrees"},"lon":{"title":"Longitude","description":"Longitude in degrees.","$ref":"#/$defs/used-properties-only/$defs/degrees"}}}}},"contact":{"title":"Contact","description":"Contact details.","type":"object","properties":{"email":{"title":"E-mail","description":"An e-mail address.","type":"string","format":"email"},"phone":{"title":"Phone","description":"A phone number.","type":"string"}}},"unusedTopLevel":{"title":"Unused top level","description":"A property nothing refers to.","$ref":"#/$defs/unused"}},"$defs":{"degrees":{"title":"Degrees","description":"An angle in degrees.","type":"number","minimum":-180,"maximum":180},"unusedDef":{"title":"Unused def","description":"A definition nothing reachable refers to.","type":"null"}}},"used-subdefs-only":{"title":"Used sub-defs only","description":"A module of which only individual definitions are referenced.","type":"object","properties":{"body":{"title":"Body","description":"The body of this module.","$ref":"#/$defs/unused"}},"$defs":{"kept":{"title":"Kept","description":"A definition that is reached directly from the root schema.","type":"object","properties":{"shared":{"title":"Shared","description":"A value defined by another module.","$ref":"#/$defs/used-whole/$defs/sharedByOther"},"fromMain":{"title":"From main","description":"A value defined by the root schema.","$ref":"#/$defs/main/$defs/mainLocal"},"chain":{"title":"Chain","description":"A value defined further down in this module.","$ref":"#/$defs/used-subdefs-only/$defs/keptTransitively"}}},"keptEmpty":{},"keptCommentOnly":{},"keptAnnotationsOnly":{"title":"Kept annotations only","description":"A schema that is just an annotation."},"keptTransitively":{"title":"Kept transitively","description":"A string value.","type":"string"},"dropped":{"title":"Dropped","description":"A definition nothing reachable refers to.","type":"null"}}},"used-whole":{"title":"Used whole","description":"A module that is referenced in its entirety.","type":"object","properties":{"name":{"title":"Name","description":"A display name.","type":"string","$defs":{"nestedDropped":{"title":"Nested dropped","description":"An unreferenced nested definition.","type":"null"}}},"withDefs":{"title":"With defs","description":"A property that hosts its own definitions.","anyOf":[{"title":"Is string","type":"string"},{"title":"Non-zero number","type":"number","if":{"const":0,"$defs":{"unused":{"title":"Unused","type":"null"}}},"then":false,"else":true,"$defs":{"unusedPositive":{"title":"Number > 0","type":"number","exclusiveMinimum":0},"unusedNegative":{"title":"Number < 0","type":"number","exclusiveMaximum":0}}}],"$defs":{"nestedKept":{"title":"Nested kept","description":"A referenced nested definition.","type":"integer"},"nestedDropped":{"title":"Nested dropped","description":"An unreferenced nested definition.","type":"null"}}},"inner":{"title":"Inner","description":"Sibling annotations next to a $ref: title/description are kept, while the sibling $comment is removed in the minified output.","$ref":"#/$defs/used-whole/$defs/inner"},"license":{"title":"License","description":"The license of this item.","$ref":"externals/not-bundled.schema.json#/$defs/licenseId"},"remote":{"title":"Remote","description":"A thing defined by a remote schema.","$ref":"https://example.com/remote.schema.json#/$defs/thing"},"inArray":{"title":"In array","description":"A composition of several referenced schemas.","allOf":[{"$ref":"#/$defs/used-whole/$defs/inner"},{"$ref":"#/$defs/used-subdefs-only/$defs/keptTransitively"},{"$ref":"externals/not-bundled.schema.json#/$defs/licenseId"}]}},"$defs":{"inner":{"title":"Inner","description":"An integer value.","type":"integer"},"unused":{"title":"Unused","description":"A flag.","type":"boolean"},"sharedByOther":{"title":"Shared by other","description":"A numeric value shared across modules.","type":"number"}}},"used-whole-data":{"title":"Used whole data","description":"A module referenced in its entirety that carries data-bearing keywords inside reached schemas.","type":"object","required":["status","level"],"examples":[{"status":"on","level":1},{"status":"off","level":2}],"default":{"status":"on","level":1},"properties":{"status":{"title":"Status","description":"A status value.","type":"string","enum":["on","off"],"meta:enum":{"on":"It is on.","off":"It is off."}},"level":{"title":"Level","description":"A fixed level.","type":"integer","const":1,"examples":[1],"default":1},"flags":{"title":"Flags","description":"An object with boolean-schema properties.","type":"object","required":["anything"],"properties":{"anything":true,"nothing":false},"additionalProperties":false},"tags":{"title":"Tags","description":"A list of tags.","type":"array","uniqueItems":true,"minItems":0,"items":{"title":"Tag","description":"A single tag.","type":"string"}}},"$defs":{"unusedDef":{"title":"Unused def","description":"A definition nothing reachable refers to.","type":"null"}}}}} \ No newline at end of file diff --git a/tools/src/main/js/bundler/test/snapshot/main-bundled.schema.json b/tools/src/main/js/bundler/test/snapshot/main-bundled.schema.json index 5b2b34c3..1acaa6bd 100644 --- a/tools/src/main/js/bundler/test/snapshot/main-bundled.schema.json +++ b/tools/src/main/js/bundler/test/snapshot/main-bundled.schema.json @@ -119,6 +119,12 @@ "title": "Annotated value", "description": "An annotation-only schema.", "$ref": "#/$defs/used-subdefs-only/$defs/keptAnnotationsOnly" + }, + "customDef": { + "$comment": "Ref into a NON-STANDARD definitions container (`custom-defs`, not `$defs`) of a module, which itself refs into yet another non-standard container (`even-more-custom-defs`). Both must be followed; unreferenced siblings in both containers and the module body are dropped.", + "title": "Custom def", + "description": "A value defined in a non-standard definitions container of another module.", + "$ref": "#/$defs/used-custom-defs/custom-defs/customKept" } }, "$defs": { @@ -287,6 +293,55 @@ } } }, + "used-custom-defs": { + "title": "Used custom defs", + "description": "A module whose definitions live in non-standard containers.", + "type": "object", + "properties": { + "body": { + "$comment": "Lives in the dropped body -> this ref must NOT make `custom-defs/customDropped` reachable.", + "title": "Body", + "description": "The body of this module.", + "$ref": "#/$defs/used-custom-defs/custom-defs/customDropped" + } + }, + "custom-defs": { + "customKept": { + "$comment": "Reached from main via a JSON pointer into a non-`$defs` container. Transitively keeps `even-more-custom-defs/deepKept`.", + "title": "Custom kept", + "description": "A definition in a non-standard container, reached from the root schema.", + "type": "object", + "properties": { + "deep": { + "$comment": "Internal ref into ANOTHER non-standard container -> must be rewired and keep `deepKept`.", + "title": "Deep", + "description": "A value defined in an even more custom container.", + "$ref": "#/$defs/used-custom-defs/even-more-custom-defs/deepKept" + } + } + }, + "customDropped": { + "$comment": "Only referenced from the dropped body -> shaken away.", + "title": "Custom dropped", + "description": "A definition nothing reachable refers to.", + "type": "null" + } + }, + "even-more-custom-defs": { + "deepKept": { + "$comment": "Only reachable via `custom-defs/customKept`.", + "title": "Deep kept", + "description": "A string value.", + "type": "string" + }, + "deepDropped": { + "$comment": "Not reachable -> shaken away.", + "title": "Deep dropped", + "description": "A definition nothing reachable refers to.", + "type": "null" + } + } + }, "used-properties-only": { "title": "Used properties only", "description": "A module of which only nested properties are referenced.", diff --git a/tools/src/main/js/bundler/test/snapshot/report.json b/tools/src/main/js/bundler/test/snapshot/report.json index e07b8b94..b55126cf 100644 --- a/tools/src/main/js/bundler/test/snapshot/report.json +++ b/tools/src/main/js/bundler/test/snapshot/report.json @@ -21,6 +21,10 @@ "modules/used-array-items.schema.json", "#/$defs/used-array-items" ], + [ + "modules/used-custom-defs.schema.json", + "#/$defs/used-custom-defs" + ], [ "modules/used-properties-only.schema.json", "#/$defs/used-properties-only" @@ -75,6 +79,18 @@ "modules/used-array-items.schema.json#/properties/combined/allOf/3", "#/$defs/used-array-items/properties/combined/allOf/3" ], + [ + "modules/used-custom-defs.schema.json#/custom-defs/customDropped", + "#/$defs/used-custom-defs/custom-defs/customDropped" + ], + [ + "modules/used-custom-defs.schema.json#/custom-defs/customKept", + "#/$defs/used-custom-defs/custom-defs/customKept" + ], + [ + "modules/used-custom-defs.schema.json#/even-more-custom-defs/deepKept", + "#/$defs/used-custom-defs/even-more-custom-defs/deepKept" + ], [ "modules/used-properties-only.schema.json#/$defs/degrees", "#/$defs/used-properties-only/$defs/degrees" From d4fae384b35bd1afd13c29393b624caafc9033b9 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Mon, 26 Oct 2026 14:22:12 +0100 Subject: [PATCH 3/3] wip Signed-off-by: Jan Kowalleck --- tools/src/main/js/bundler/src/bundle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/js/bundler/src/bundle.js b/tools/src/main/js/bundler/src/bundle.js index e2b29f4e..90211235 100644 --- a/tools/src/main/js/bundler/src/bundle.js +++ b/tools/src/main/js/bundler/src/bundle.js @@ -181,7 +181,7 @@ export const bundle = async (entryFile, includeFiles, targetFile) => { rewiredMap.set(refJoin({path: refFile, frag}), rewired); if (rewiredRS.path) { externals.add(rewired); - } else { + } else if (refMS) { const tSchema = objectGetOwnPath(refMS.schema, jsonPointer2stack(frag ?? ROOT_POINTER)); if (tSchema === PATH_NOT_FOUND) { throw RangeError(`Missing target for ref: ${v}`);