@apidevtools/json-schema-ref-parser 15.3.3 → 15.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/bundle.js +12 -7
- package/dist/lib/dereference.js +23 -10
- package/dist/lib/pointer.js +11 -10
- package/dist/lib/ref.d.ts +2 -2
- package/dist/lib/ref.js +2 -27
- package/dist/lib/refs.d.ts +0 -2
- package/dist/lib/refs.js +3 -13
- package/dist/lib/resolve-external.js +8 -3
- package/dist/lib/util/schema-resources.js +3 -28
- package/dist/lib/util/url.js +4 -1
- package/lib/bundle.ts +13 -7
- package/lib/dereference.ts +24 -10
- package/lib/pointer.ts +12 -10
- package/lib/ref.ts +1 -34
- package/lib/refs.ts +3 -16
- package/lib/resolve-external.ts +8 -3
- package/lib/util/schema-resources.ts +3 -39
- package/lib/util/url.ts +5 -1
- package/package.json +4 -4
package/dist/lib/bundle.js
CHANGED
|
@@ -12,9 +12,12 @@ import { getSchemaBasePath } from "./util/schema-resources.js";
|
|
|
12
12
|
*/
|
|
13
13
|
function bundle(parser, options) {
|
|
14
14
|
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
15
|
+
const rootScopeBase = parser.$refs._root$Ref.dynamicIdScope
|
|
16
|
+
? getSchemaBasePath(parser.$refs._root$Ref.path, parser.schema)
|
|
17
|
+
: parser.$refs._root$Ref.path;
|
|
15
18
|
// Build an inventory of all $ref pointers in the JSON Schema
|
|
16
19
|
const inventory = [];
|
|
17
|
-
crawl(parser, "schema", parser.$refs._root$Ref.path + "#",
|
|
20
|
+
crawl(parser, "schema", parser.$refs._root$Ref.path + "#", rootScopeBase, parser.$refs._root$Ref.dynamicIdScope, "#", 0, inventory, parser.$refs, options);
|
|
18
21
|
// Get the root schema's $id (if any) for qualifying refs inside sub-schemas with their own $id
|
|
19
22
|
const rootId = parser.schema && typeof parser.schema === "object" && "$id" in parser.schema
|
|
20
23
|
? parser.schema.$id
|
|
@@ -44,8 +47,8 @@ function crawl(parent, key, path, scopeBase, dynamicIdScope, pathFromRoot, indir
|
|
|
44
47
|
const bundleOptions = (options.bundle || {});
|
|
45
48
|
const isExcludedPath = bundleOptions.excludedPathMatcher || (() => false);
|
|
46
49
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
47
|
-
const currentScopeBase =
|
|
48
|
-
if ($Ref.isAllowed$Ref(obj
|
|
50
|
+
const currentScopeBase = scopeBase;
|
|
51
|
+
if ($Ref.isAllowed$Ref(obj)) {
|
|
49
52
|
inventory$Ref(parent, key, path, currentScopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options);
|
|
50
53
|
}
|
|
51
54
|
else {
|
|
@@ -71,12 +74,14 @@ function crawl(parent, key, path, scopeBase, dynamicIdScope, pathFromRoot, indir
|
|
|
71
74
|
const keyPath = Pointer.join(path, key);
|
|
72
75
|
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
73
76
|
const value = obj[key];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
const childScopeBase = dynamicIdScope && value && typeof value === "object" && !ArrayBuffer.isView(value)
|
|
78
|
+
? getSchemaBasePath(currentScopeBase, value)
|
|
79
|
+
: currentScopeBase;
|
|
80
|
+
if ($Ref.isAllowed$Ref(value)) {
|
|
81
|
+
inventory$Ref(obj, key, keyPath, childScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
77
82
|
}
|
|
78
83
|
else {
|
|
79
|
-
crawl(obj, key, keyPath,
|
|
84
|
+
crawl(obj, key, keyPath, childScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
80
85
|
}
|
|
81
86
|
// We need to ensure that we have an object to work with here because we may be crawling
|
|
82
87
|
// an `examples` schema and `value` may be nullish.
|
package/dist/lib/dereference.js
CHANGED
|
@@ -13,8 +13,11 @@ export default dereference;
|
|
|
13
13
|
*/
|
|
14
14
|
function dereference(parser, options) {
|
|
15
15
|
const start = Date.now();
|
|
16
|
+
const rootScopeBase = parser.$refs._root$Ref.dynamicIdScope
|
|
17
|
+
? getSchemaBasePath(parser.$refs._root$Ref.path, parser.schema)
|
|
18
|
+
: parser.$refs._root$Ref.path;
|
|
16
19
|
// console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
17
|
-
const dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path,
|
|
20
|
+
const dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, rootScopeBase, parser.$refs._root$Ref.dynamicIdScope, "#", new Set(), new Set(), new Map(), parser.$refs, options, start, 0);
|
|
18
21
|
parser.$refs.circular = dereferenced.circular;
|
|
19
22
|
parser.schema = dereferenced.value;
|
|
20
23
|
}
|
|
@@ -52,8 +55,8 @@ function crawl(obj, path, scopeBase, dynamicIdScope, pathFromRoot, parents, proc
|
|
|
52
55
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
53
56
|
parents.add(obj);
|
|
54
57
|
processedObjects.add(obj);
|
|
55
|
-
const currentScopeBase =
|
|
56
|
-
if ($Ref.isAllowed$Ref(obj, options
|
|
58
|
+
const currentScopeBase = scopeBase;
|
|
59
|
+
if ($Ref.isAllowed$Ref(obj, options)) {
|
|
57
60
|
dereferenced = dereference$Ref(obj, path, currentScopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
58
61
|
result.circular = dereferenced.circular;
|
|
59
62
|
result.value = dereferenced.value;
|
|
@@ -67,10 +70,12 @@ function crawl(obj, path, scopeBase, dynamicIdScope, pathFromRoot, parents, proc
|
|
|
67
70
|
continue;
|
|
68
71
|
}
|
|
69
72
|
const value = obj[key];
|
|
73
|
+
const childScopeBase = dynamicIdScope && value && typeof value === "object" && !ArrayBuffer.isView(value)
|
|
74
|
+
? getSchemaBasePath(currentScopeBase, value)
|
|
75
|
+
: currentScopeBase;
|
|
70
76
|
let circular;
|
|
71
|
-
if ($Ref.isAllowed$Ref(value, options
|
|
72
|
-
|
|
73
|
-
dereferenced = dereference$Ref(value, keyPath, valueScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
77
|
+
if ($Ref.isAllowed$Ref(value, options)) {
|
|
78
|
+
dereferenced = dereference$Ref(value, keyPath, childScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
74
79
|
circular = dereferenced.circular;
|
|
75
80
|
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
76
81
|
if (obj[key] !== dereferenced.value) {
|
|
@@ -107,7 +112,7 @@ function crawl(obj, path, scopeBase, dynamicIdScope, pathFromRoot, parents, proc
|
|
|
107
112
|
}
|
|
108
113
|
else {
|
|
109
114
|
if (!parents.has(value)) {
|
|
110
|
-
dereferenced = crawl(value, keyPath,
|
|
115
|
+
dereferenced = crawl(value, keyPath, childScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
111
116
|
circular = dereferenced.circular;
|
|
112
117
|
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
113
118
|
if (obj[key] !== dereferenced.value) {
|
|
@@ -153,10 +158,18 @@ function dereference$Ref($ref, path, scopeBase, dynamicIdScope, pathFromRoot, pa
|
|
|
153
158
|
// If the cached object however is _not_ circular and there are additional keys alongside our
|
|
154
159
|
// `$ref` pointer here we should merge them back in and return that.
|
|
155
160
|
if (!cache.circular) {
|
|
156
|
-
|
|
161
|
+
const refKeys = Object.keys($ref);
|
|
162
|
+
if (refKeys.length > 1) {
|
|
163
|
+
const extraKeys = {};
|
|
164
|
+
for (const key of refKeys) {
|
|
165
|
+
if (key !== "$ref" && !(key in cache.value)) {
|
|
166
|
+
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
167
|
+
extraKeys[key] = $ref[key];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
157
170
|
return {
|
|
158
171
|
circular: cache.circular,
|
|
159
|
-
value:
|
|
172
|
+
value: Object.assign({}, cache.value, extraKeys),
|
|
160
173
|
};
|
|
161
174
|
}
|
|
162
175
|
return cache;
|
|
@@ -202,7 +215,7 @@ function dereference$Ref($ref, path, scopeBase, dynamicIdScope, pathFromRoot, pa
|
|
|
202
215
|
foundCircularReference(path, $refs, options);
|
|
203
216
|
}
|
|
204
217
|
// Dereference the JSON reference
|
|
205
|
-
let dereferencedValue = $Ref.dereference($ref, pointer.value, options
|
|
218
|
+
let dereferencedValue = $Ref.dereference($ref, pointer.value, options);
|
|
206
219
|
// Crawl the dereferenced value (unless it's circular)
|
|
207
220
|
if (!circular) {
|
|
208
221
|
// Determine if the dereferenced value is circular
|
package/dist/lib/pointer.js
CHANGED
|
@@ -74,7 +74,7 @@ class Pointer {
|
|
|
74
74
|
const found = [];
|
|
75
75
|
// Crawl the object, one token at a time
|
|
76
76
|
this.value = unwrapOrThrow(obj);
|
|
77
|
-
if (this.$ref.dynamicIdScope) {
|
|
77
|
+
if (this.$ref.dynamicIdScope && !isAliasedResource(this.$ref)) {
|
|
78
78
|
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
79
79
|
}
|
|
80
80
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -163,7 +163,7 @@ class Pointer {
|
|
|
163
163
|
}
|
|
164
164
|
// Crawl the object, one token at a time
|
|
165
165
|
this.value = unwrapOrThrow(obj);
|
|
166
|
-
if (this.$ref.dynamicIdScope) {
|
|
166
|
+
if (this.$ref.dynamicIdScope && !isAliasedResource(this.$ref)) {
|
|
167
167
|
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
168
168
|
}
|
|
169
169
|
for (let i = 0; i < tokens.length - 1; i++) {
|
|
@@ -256,7 +256,7 @@ class Pointer {
|
|
|
256
256
|
*/
|
|
257
257
|
function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
258
258
|
// Is the value a JSON reference? (and allowed?)
|
|
259
|
-
if ($Ref.isAllowed$Ref(pointer.value, options
|
|
259
|
+
if ($Ref.isAllowed$Ref(pointer.value, options)) {
|
|
260
260
|
const resolutionBase = pointer.$ref.dynamicIdScope ? pointer.scopeBase : pointer.path;
|
|
261
261
|
const $refPath = url.resolve(resolutionBase, pointer.value.$ref);
|
|
262
262
|
if ($refPath === pointer.path && !isRootPath(pathFromRoot)) {
|
|
@@ -272,10 +272,7 @@ function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
|
272
272
|
if ($Ref.isExtended$Ref(pointer.value)) {
|
|
273
273
|
// This JSON reference "extends" the resolved value, rather than simply pointing to it.
|
|
274
274
|
// So the resolved path does NOT change. Just the value does.
|
|
275
|
-
pointer.value = $Ref.dereference(pointer.value, resolved.value, options
|
|
276
|
-
if (pointer.$ref.dynamicIdScope) {
|
|
277
|
-
pointer.scopeBase = getSchemaBasePath(pointer.scopeBase, pointer.value);
|
|
278
|
-
}
|
|
275
|
+
pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
|
|
279
276
|
return false;
|
|
280
277
|
}
|
|
281
278
|
else {
|
|
@@ -283,9 +280,10 @@ function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
|
283
280
|
pointer.$ref = resolved.$ref;
|
|
284
281
|
pointer.path = resolved.path;
|
|
285
282
|
pointer.value = resolved.value;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
283
|
+
// `pointer.$ref.path` is already the canonical location of the resolved resource.
|
|
284
|
+
// Re-applying the resource's own `$id` here would duplicate nested path segments
|
|
285
|
+
// such as `nested/nested/foo.json`.
|
|
286
|
+
pointer.scopeBase = pointer.$ref.path;
|
|
289
287
|
}
|
|
290
288
|
return true;
|
|
291
289
|
}
|
|
@@ -327,3 +325,6 @@ function unwrapOrThrow(value) {
|
|
|
327
325
|
function isRootPath(pathFromRoot) {
|
|
328
326
|
return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
|
|
329
327
|
}
|
|
328
|
+
function isAliasedResource($ref) {
|
|
329
|
+
return Boolean($ref.path && $ref.path in $ref.$refs._aliases);
|
|
330
|
+
}
|
package/dist/lib/ref.d.ts
CHANGED
|
@@ -114,7 +114,7 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
114
114
|
* @param options
|
|
115
115
|
* @returns
|
|
116
116
|
*/
|
|
117
|
-
static isAllowed$Ref<S extends object = JSONSchema>(value: unknown, options?: ParserOptions<S
|
|
117
|
+
static isAllowed$Ref<S extends object = JSONSchema>(value: unknown, options?: ParserOptions<S>): true | undefined;
|
|
118
118
|
/**
|
|
119
119
|
* Determines whether the given value is a JSON reference that "extends" its resolved value.
|
|
120
120
|
* That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
|
|
@@ -182,6 +182,6 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
182
182
|
* @param options - The options
|
|
183
183
|
* @returns - Returns the dereferenced value
|
|
184
184
|
*/
|
|
185
|
-
static dereference<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>($ref: $Ref<S, O>, resolvedValue: S, options?: O
|
|
185
|
+
static dereference<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>($ref: $Ref<S, O>, resolvedValue: S, options?: O): S;
|
|
186
186
|
}
|
|
187
187
|
export default $Ref;
|
package/dist/lib/ref.js
CHANGED
|
@@ -170,16 +170,12 @@ class $Ref {
|
|
|
170
170
|
* @param options
|
|
171
171
|
* @returns
|
|
172
172
|
*/
|
|
173
|
-
static isAllowed$Ref(value, options
|
|
173
|
+
static isAllowed$Ref(value, options) {
|
|
174
174
|
if (this.is$Ref(value)) {
|
|
175
175
|
if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") {
|
|
176
176
|
// It's a JSON Pointer reference, which is always allowed
|
|
177
177
|
return true;
|
|
178
178
|
}
|
|
179
|
-
else if (allowPlainNameFragments && value.$ref[0] === "#") {
|
|
180
|
-
// JSON Schema 2019-09+ allows plain-name fragments declared via $anchor/$dynamicAnchor
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
179
|
else if (value.$ref[0] !== "#" && (!options || options.resolve?.external)) {
|
|
184
180
|
// It's an external reference, which is allowed by the options
|
|
185
181
|
return true;
|
|
@@ -256,10 +252,7 @@ class $Ref {
|
|
|
256
252
|
* @param options - The options
|
|
257
253
|
* @returns - Returns the dereferenced value
|
|
258
254
|
*/
|
|
259
|
-
static dereference($ref, resolvedValue, options
|
|
260
|
-
if ($Ref.isExtended$Ref($ref) && useSpecCompliantRefSiblings) {
|
|
261
|
-
return preserveRefSiblings($ref, resolvedValue);
|
|
262
|
-
}
|
|
255
|
+
static dereference($ref, resolvedValue, options) {
|
|
263
256
|
if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
|
|
264
257
|
const merged = {};
|
|
265
258
|
for (const key of Object.keys($ref)) {
|
|
@@ -294,24 +287,6 @@ class $Ref {
|
|
|
294
287
|
}
|
|
295
288
|
}
|
|
296
289
|
}
|
|
297
|
-
function preserveRefSiblings(refValue, resolvedValue) {
|
|
298
|
-
const preserved = {};
|
|
299
|
-
const existingAllOf = "allOf" in refValue ? refValue.allOf : undefined;
|
|
300
|
-
for (const key of Object.keys(refValue)) {
|
|
301
|
-
if (key !== "$ref" && key !== "allOf") {
|
|
302
|
-
preserved[key] = refValue[key];
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const allOf = [resolvedValue];
|
|
306
|
-
if (Array.isArray(existingAllOf)) {
|
|
307
|
-
allOf.push(...existingAllOf);
|
|
308
|
-
}
|
|
309
|
-
else if (existingAllOf !== undefined) {
|
|
310
|
-
allOf.push(existingAllOf);
|
|
311
|
-
}
|
|
312
|
-
preserved.allOf = allOf;
|
|
313
|
-
return preserved;
|
|
314
|
-
}
|
|
315
290
|
function deepMerge(target, source) {
|
|
316
291
|
//return {...target, ...source};
|
|
317
292
|
// If either isn't an object, just return source (overwrite)
|
package/dist/lib/refs.d.ts
CHANGED
|
@@ -80,7 +80,6 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
80
80
|
*/
|
|
81
81
|
_add(path: string): $Ref<S, O>;
|
|
82
82
|
_addAlias(path: string, value: S, pathType?: string | unknown, dynamicIdScope?: boolean): $Ref<S, O>;
|
|
83
|
-
_addExactAlias(path: string, targetPath: string): string;
|
|
84
83
|
/**
|
|
85
84
|
* Resolves the given JSON reference.
|
|
86
85
|
*
|
|
@@ -99,7 +98,6 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
99
98
|
*/
|
|
100
99
|
_$refs: $RefsMap<S, O>;
|
|
101
100
|
_aliases: $RefsMap<S, O>;
|
|
102
|
-
_exactAliases: Record<string, string>;
|
|
103
101
|
/**
|
|
104
102
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
105
103
|
*
|
package/dist/lib/refs.js
CHANGED
|
@@ -99,7 +99,7 @@ export default class $Refs {
|
|
|
99
99
|
*/
|
|
100
100
|
_get$Ref(path) {
|
|
101
101
|
path = url.resolve(this._root$Ref.path, path);
|
|
102
|
-
return this._getRef(
|
|
102
|
+
return this._getRef(path);
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Creates a new {@link $Ref} object and adds it to this {@link $Refs} object.
|
|
@@ -127,13 +127,6 @@ export default class $Refs {
|
|
|
127
127
|
this._aliases[withoutHash] = $ref;
|
|
128
128
|
return $ref;
|
|
129
129
|
}
|
|
130
|
-
_addExactAlias(path, targetPath) {
|
|
131
|
-
if (!path || this._exactAliases[path]) {
|
|
132
|
-
return this._exactAliases[path];
|
|
133
|
-
}
|
|
134
|
-
this._exactAliases[path] = targetPath;
|
|
135
|
-
return targetPath;
|
|
136
|
-
}
|
|
137
130
|
/**
|
|
138
131
|
* Resolves the given JSON reference.
|
|
139
132
|
*
|
|
@@ -145,12 +138,11 @@ export default class $Refs {
|
|
|
145
138
|
*/
|
|
146
139
|
_resolve(path, pathFromRoot, options) {
|
|
147
140
|
const absPath = url.resolve(this._root$Ref.path, path);
|
|
148
|
-
const
|
|
149
|
-
const $ref = this._getRef(canonicalPath);
|
|
141
|
+
const $ref = this._getRef(absPath);
|
|
150
142
|
if (!$ref) {
|
|
151
143
|
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
152
144
|
}
|
|
153
|
-
return $ref.resolve(
|
|
145
|
+
return $ref.resolve(absPath, options, path, pathFromRoot);
|
|
154
146
|
}
|
|
155
147
|
/**
|
|
156
148
|
* A map of paths/urls to {@link $Ref} objects
|
|
@@ -160,7 +152,6 @@ export default class $Refs {
|
|
|
160
152
|
*/
|
|
161
153
|
_$refs = {};
|
|
162
154
|
_aliases = {};
|
|
163
|
-
_exactAliases = {};
|
|
164
155
|
/**
|
|
165
156
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
166
157
|
*
|
|
@@ -177,7 +168,6 @@ export default class $Refs {
|
|
|
177
168
|
this.circular = false;
|
|
178
169
|
this._$refs = {};
|
|
179
170
|
this._aliases = {};
|
|
180
|
-
this._exactAliases = {};
|
|
181
171
|
// @ts-ignore
|
|
182
172
|
this._root$Ref = null;
|
|
183
173
|
}
|
|
@@ -20,8 +20,11 @@ function resolveExternal(parser, options) {
|
|
|
20
20
|
return Promise.resolve();
|
|
21
21
|
}
|
|
22
22
|
try {
|
|
23
|
+
const rootScopeBase = parser.$refs._root$Ref.dynamicIdScope
|
|
24
|
+
? getSchemaBasePath(parser.$refs._root$Ref.path, parser.schema)
|
|
25
|
+
: parser.$refs._root$Ref.path;
|
|
23
26
|
// console.log('Resolving $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
24
|
-
const promises = crawl(parser.schema, parser.$refs._root$Ref.path + "#",
|
|
27
|
+
const promises = crawl(parser.schema, parser.$refs._root$Ref.path + "#", rootScopeBase, parser.$refs._root$Ref.dynamicIdScope, parser.$refs, options);
|
|
25
28
|
return Promise.all(promises);
|
|
26
29
|
}
|
|
27
30
|
catch (e) {
|
|
@@ -49,7 +52,7 @@ function crawl(obj, path, scopeBase, dynamicIdScope, $refs, options, seen, exter
|
|
|
49
52
|
let promises = [];
|
|
50
53
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
|
|
51
54
|
seen.add(obj); // Track previously seen objects to avoid infinite recursion
|
|
52
|
-
const currentScopeBase =
|
|
55
|
+
const currentScopeBase = scopeBase;
|
|
53
56
|
if ($Ref.isExternal$Ref(obj)) {
|
|
54
57
|
promises.push(resolve$Ref(obj, path, currentScopeBase, dynamicIdScope, $refs, options));
|
|
55
58
|
}
|
|
@@ -57,7 +60,9 @@ function crawl(obj, path, scopeBase, dynamicIdScope, $refs, options, seen, exter
|
|
|
57
60
|
for (const key of keys) {
|
|
58
61
|
const keyPath = Pointer.join(path, key);
|
|
59
62
|
const value = obj[key];
|
|
60
|
-
const childScopeBase = dynamicIdScope &&
|
|
63
|
+
const childScopeBase = dynamicIdScope && value && typeof value === "object" && !ArrayBuffer.isView(value)
|
|
64
|
+
? getSchemaBasePath(currentScopeBase, value)
|
|
65
|
+
: currentScopeBase;
|
|
61
66
|
promises = promises.concat(crawl(value, keyPath, childScopeBase, dynamicIdScope, $refs, options, seen, external));
|
|
62
67
|
}
|
|
63
68
|
}
|
|
@@ -20,22 +20,20 @@ export function registerSchemaResources($refs, basePath, value, pathType, dynami
|
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
const seen = new Set();
|
|
23
|
-
const visit = (node, scopeBase
|
|
23
|
+
const visit = (node, scopeBase) => {
|
|
24
24
|
if (!node || typeof node !== "object" || ArrayBuffer.isView(node) || seen.has(node)) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
seen.add(node);
|
|
28
28
|
const nextScopeBase = getSchemaBasePath(scopeBase, node);
|
|
29
|
-
const resourcePointerTokens = nextScopeBase === scopeBase ? pointerTokens : [];
|
|
30
29
|
if (nextScopeBase !== scopeBase) {
|
|
31
30
|
$refs._addAlias(nextScopeBase, node, pathType, dynamicIdScope);
|
|
32
31
|
}
|
|
33
|
-
registerAnchorAliases($refs, nextScopeBase, resourcePointerTokens, node);
|
|
34
32
|
for (const key of Object.keys(node)) {
|
|
35
|
-
visit(node[key], nextScopeBase
|
|
33
|
+
visit(node[key], nextScopeBase);
|
|
36
34
|
}
|
|
37
35
|
};
|
|
38
|
-
visit(value, basePath
|
|
36
|
+
visit(value, basePath);
|
|
39
37
|
}
|
|
40
38
|
function getSchemaId(value) {
|
|
41
39
|
if (value &&
|
|
@@ -47,26 +45,3 @@ function getSchemaId(value) {
|
|
|
47
45
|
}
|
|
48
46
|
return undefined;
|
|
49
47
|
}
|
|
50
|
-
function registerAnchorAliases($refs, scopeBase, pointerTokens, value) {
|
|
51
|
-
if (!value || typeof value !== "object" || ArrayBuffer.isView(value)) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const resourceBase = url.stripHash(scopeBase);
|
|
55
|
-
const targetPath = pointerTokens.length > 0 ? joinPointerPath(resourceBase, pointerTokens) : `${resourceBase}#`;
|
|
56
|
-
const anchors = [
|
|
57
|
-
value.$anchor,
|
|
58
|
-
value.$dynamicAnchor,
|
|
59
|
-
];
|
|
60
|
-
for (const anchor of anchors) {
|
|
61
|
-
if (typeof anchor === "string" && anchor.length > 0) {
|
|
62
|
-
$refs._addExactAlias(`${resourceBase}#${anchor}`, targetPath);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function joinPointerPath(basePath, tokens) {
|
|
67
|
-
let path = `${basePath}#`;
|
|
68
|
-
for (const token of tokens) {
|
|
69
|
-
path += `/${token.replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
70
|
-
}
|
|
71
|
-
return path;
|
|
72
|
-
}
|
package/dist/lib/util/url.js
CHANGED
|
@@ -4,7 +4,7 @@ const protocolPattern = /^(\w{2,}):\/\//i;
|
|
|
4
4
|
const jsonPointerSlash = /~1/g;
|
|
5
5
|
const jsonPointerTilde = /~0/g;
|
|
6
6
|
import { isWindows } from "./is-windows.js";
|
|
7
|
-
const isAbsoluteWin32Path = /^[a-zA-Z]
|
|
7
|
+
const isAbsoluteWin32Path = /^[a-zA-Z]:[\\/]/;
|
|
8
8
|
// RegExp patterns to URL-encode special characters in local filesystem paths
|
|
9
9
|
const urlEncodePatterns = [
|
|
10
10
|
[/\?/g, "%3F"],
|
|
@@ -381,6 +381,9 @@ export function fromFileSystemPath(path) {
|
|
|
381
381
|
* Converts a URL to a local filesystem path.
|
|
382
382
|
*/
|
|
383
383
|
export function toFileSystemPath(path, keepFileProtocol) {
|
|
384
|
+
// Bare "%" characters are valid in filesystem paths, but they make `decodeURI` throw.
|
|
385
|
+
// Escape only the non-encoded ones so percent-encoded sequences still decode normally.
|
|
386
|
+
path = path.replace(/%(?![0-9A-Fa-f]{2})/g, "%25");
|
|
384
387
|
// Step 1: `decodeURI` will decode characters such as Cyrillic characters, spaces, etc.
|
|
385
388
|
path = decodeURI(path);
|
|
386
389
|
// Step 2: Manually decode characters that are not decoded by `decodeURI`.
|
package/lib/bundle.ts
CHANGED
|
@@ -35,6 +35,9 @@ function bundle<S extends object = JSONSchema, O extends ParserOptions<S> = Pars
|
|
|
35
35
|
options: O,
|
|
36
36
|
) {
|
|
37
37
|
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
38
|
+
const rootScopeBase = parser.$refs._root$Ref.dynamicIdScope
|
|
39
|
+
? getSchemaBasePath(parser.$refs._root$Ref.path!, parser.schema)
|
|
40
|
+
: parser.$refs._root$Ref.path!;
|
|
38
41
|
|
|
39
42
|
// Build an inventory of all $ref pointers in the JSON Schema
|
|
40
43
|
const inventory: InventoryEntry[] = [];
|
|
@@ -42,7 +45,7 @@ function bundle<S extends object = JSONSchema, O extends ParserOptions<S> = Pars
|
|
|
42
45
|
parser,
|
|
43
46
|
"schema",
|
|
44
47
|
parser.$refs._root$Ref.path + "#",
|
|
45
|
-
|
|
48
|
+
rootScopeBase,
|
|
46
49
|
parser.$refs._root$Ref.dynamicIdScope,
|
|
47
50
|
"#",
|
|
48
51
|
0,
|
|
@@ -96,8 +99,8 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
96
99
|
const isExcludedPath = bundleOptions.excludedPathMatcher || (() => false);
|
|
97
100
|
|
|
98
101
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
99
|
-
const currentScopeBase =
|
|
100
|
-
if ($Ref.isAllowed$Ref(obj
|
|
102
|
+
const currentScopeBase = scopeBase;
|
|
103
|
+
if ($Ref.isAllowed$Ref(obj)) {
|
|
101
104
|
inventory$Ref(parent, key, path, currentScopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options);
|
|
102
105
|
} else {
|
|
103
106
|
// Crawl the object in a specific order that's optimized for bundling.
|
|
@@ -121,14 +124,17 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
121
124
|
const keyPath = Pointer.join(path, key);
|
|
122
125
|
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
123
126
|
const value = obj[key];
|
|
127
|
+
const childScopeBase =
|
|
128
|
+
dynamicIdScope && value && typeof value === "object" && !ArrayBuffer.isView(value)
|
|
129
|
+
? getSchemaBasePath(currentScopeBase, value)
|
|
130
|
+
: currentScopeBase;
|
|
124
131
|
|
|
125
|
-
if ($Ref.isAllowed$Ref(value
|
|
126
|
-
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
132
|
+
if ($Ref.isAllowed$Ref(value)) {
|
|
127
133
|
inventory$Ref(
|
|
128
134
|
obj,
|
|
129
135
|
key,
|
|
130
136
|
keyPath,
|
|
131
|
-
|
|
137
|
+
childScopeBase,
|
|
132
138
|
dynamicIdScope,
|
|
133
139
|
keyPathFromRoot,
|
|
134
140
|
indirections,
|
|
@@ -137,7 +143,7 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
137
143
|
options,
|
|
138
144
|
);
|
|
139
145
|
} else {
|
|
140
|
-
crawl(obj, key, keyPath,
|
|
146
|
+
crawl(obj, key, keyPath, childScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
// We need to ensure that we have an object to work with here because we may be crawling
|
package/lib/dereference.ts
CHANGED
|
@@ -21,11 +21,14 @@ function dereference<S extends object = JSONSchema, O extends ParserOptions<S> =
|
|
|
21
21
|
options: O,
|
|
22
22
|
) {
|
|
23
23
|
const start = Date.now();
|
|
24
|
+
const rootScopeBase = parser.$refs._root$Ref.dynamicIdScope
|
|
25
|
+
? getSchemaBasePath(parser.$refs._root$Ref.path!, parser.schema)
|
|
26
|
+
: parser.$refs._root$Ref.path!;
|
|
24
27
|
// console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
25
28
|
const dereferenced = crawl<S, O>(
|
|
26
29
|
parser.schema,
|
|
27
30
|
parser.$refs._root$Ref.path!,
|
|
28
|
-
|
|
31
|
+
rootScopeBase,
|
|
29
32
|
parser.$refs._root$Ref.dynamicIdScope,
|
|
30
33
|
"#",
|
|
31
34
|
new Set(),
|
|
@@ -92,9 +95,9 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
92
95
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
93
96
|
parents.add(obj);
|
|
94
97
|
processedObjects.add(obj);
|
|
95
|
-
const currentScopeBase =
|
|
98
|
+
const currentScopeBase = scopeBase;
|
|
96
99
|
|
|
97
|
-
if ($Ref.isAllowed$Ref(obj, options
|
|
100
|
+
if ($Ref.isAllowed$Ref(obj, options)) {
|
|
98
101
|
dereferenced = dereference$Ref(
|
|
99
102
|
obj,
|
|
100
103
|
path,
|
|
@@ -123,14 +126,17 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
const value = obj[key];
|
|
129
|
+
const childScopeBase =
|
|
130
|
+
dynamicIdScope && value && typeof value === "object" && !ArrayBuffer.isView(value)
|
|
131
|
+
? getSchemaBasePath(currentScopeBase, value)
|
|
132
|
+
: currentScopeBase;
|
|
126
133
|
let circular;
|
|
127
134
|
|
|
128
|
-
if ($Ref.isAllowed$Ref(value, options
|
|
129
|
-
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
135
|
+
if ($Ref.isAllowed$Ref(value, options)) {
|
|
130
136
|
dereferenced = dereference$Ref(
|
|
131
137
|
value,
|
|
132
138
|
keyPath,
|
|
133
|
-
|
|
139
|
+
childScopeBase,
|
|
134
140
|
dynamicIdScope,
|
|
135
141
|
keyPathFromRoot,
|
|
136
142
|
parents,
|
|
@@ -183,7 +189,7 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
183
189
|
dereferenced = crawl(
|
|
184
190
|
value,
|
|
185
191
|
keyPath,
|
|
186
|
-
|
|
192
|
+
childScopeBase,
|
|
187
193
|
dynamicIdScope,
|
|
188
194
|
keyPathFromRoot,
|
|
189
195
|
parents,
|
|
@@ -257,10 +263,18 @@ function dereference$Ref<S extends object = JSONSchema, O extends ParserOptions<
|
|
|
257
263
|
// If the cached object however is _not_ circular and there are additional keys alongside our
|
|
258
264
|
// `$ref` pointer here we should merge them back in and return that.
|
|
259
265
|
if (!cache.circular) {
|
|
260
|
-
|
|
266
|
+
const refKeys = Object.keys($ref);
|
|
267
|
+
if (refKeys.length > 1) {
|
|
268
|
+
const extraKeys = {};
|
|
269
|
+
for (const key of refKeys) {
|
|
270
|
+
if (key !== "$ref" && !(key in cache.value)) {
|
|
271
|
+
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
|
|
272
|
+
extraKeys[key] = $ref[key];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
261
275
|
return {
|
|
262
276
|
circular: cache.circular,
|
|
263
|
-
value:
|
|
277
|
+
value: Object.assign({}, cache.value, extraKeys),
|
|
264
278
|
};
|
|
265
279
|
}
|
|
266
280
|
|
|
@@ -310,7 +324,7 @@ function dereference$Ref<S extends object = JSONSchema, O extends ParserOptions<
|
|
|
310
324
|
}
|
|
311
325
|
|
|
312
326
|
// Dereference the JSON reference
|
|
313
|
-
let dereferencedValue = $Ref.dereference($ref, pointer.value, options
|
|
327
|
+
let dereferencedValue = $Ref.dereference($ref, pointer.value, options);
|
|
314
328
|
|
|
315
329
|
// Crawl the dereferenced value (unless it's circular)
|
|
316
330
|
if (!circular) {
|
package/lib/pointer.ts
CHANGED
|
@@ -95,7 +95,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
95
95
|
|
|
96
96
|
// Crawl the object, one token at a time
|
|
97
97
|
this.value = unwrapOrThrow(obj);
|
|
98
|
-
if (this.$ref.dynamicIdScope) {
|
|
98
|
+
if (this.$ref.dynamicIdScope && !isAliasedResource(this.$ref)) {
|
|
99
99
|
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -196,7 +196,7 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
|
|
|
196
196
|
|
|
197
197
|
// Crawl the object, one token at a time
|
|
198
198
|
this.value = unwrapOrThrow(obj);
|
|
199
|
-
if (this.$ref.dynamicIdScope) {
|
|
199
|
+
if (this.$ref.dynamicIdScope && !isAliasedResource(this.$ref)) {
|
|
200
200
|
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
201
201
|
}
|
|
202
202
|
|
|
@@ -308,7 +308,7 @@ function resolveIf$Ref<S extends object = JSONSchema, O extends ParserOptions<S>
|
|
|
308
308
|
) {
|
|
309
309
|
// Is the value a JSON reference? (and allowed?)
|
|
310
310
|
|
|
311
|
-
if ($Ref.isAllowed$Ref(pointer.value, options
|
|
311
|
+
if ($Ref.isAllowed$Ref(pointer.value, options)) {
|
|
312
312
|
const resolutionBase = pointer.$ref.dynamicIdScope ? pointer.scopeBase : pointer.path;
|
|
313
313
|
const $refPath = url.resolve(resolutionBase, pointer.value.$ref);
|
|
314
314
|
|
|
@@ -326,19 +326,17 @@ function resolveIf$Ref<S extends object = JSONSchema, O extends ParserOptions<S>
|
|
|
326
326
|
if ($Ref.isExtended$Ref(pointer.value)) {
|
|
327
327
|
// This JSON reference "extends" the resolved value, rather than simply pointing to it.
|
|
328
328
|
// So the resolved path does NOT change. Just the value does.
|
|
329
|
-
pointer.value = $Ref.dereference(pointer.value, resolved.value, options
|
|
330
|
-
if (pointer.$ref.dynamicIdScope) {
|
|
331
|
-
pointer.scopeBase = getSchemaBasePath(pointer.scopeBase, pointer.value);
|
|
332
|
-
}
|
|
329
|
+
pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
|
|
333
330
|
return false;
|
|
334
331
|
} else {
|
|
335
332
|
// Resolve the reference
|
|
336
333
|
pointer.$ref = resolved.$ref;
|
|
337
334
|
pointer.path = resolved.path;
|
|
338
335
|
pointer.value = resolved.value;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
// `pointer.$ref.path` is already the canonical location of the resolved resource.
|
|
337
|
+
// Re-applying the resource's own `$id` here would duplicate nested path segments
|
|
338
|
+
// such as `nested/nested/foo.json`.
|
|
339
|
+
pointer.scopeBase = pointer.$ref.path!;
|
|
342
340
|
}
|
|
343
341
|
|
|
344
342
|
return true;
|
|
@@ -385,3 +383,7 @@ function unwrapOrThrow(value: unknown) {
|
|
|
385
383
|
function isRootPath(pathFromRoot: string | unknown): boolean {
|
|
386
384
|
return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
|
|
387
385
|
}
|
|
386
|
+
|
|
387
|
+
function isAliasedResource($ref: $Ref<any, any>) {
|
|
388
|
+
return Boolean($ref.path && $ref.path in $ref.$refs._aliases);
|
|
389
|
+
}
|
package/lib/ref.ts
CHANGED
|
@@ -199,18 +199,11 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
199
199
|
* @param options
|
|
200
200
|
* @returns
|
|
201
201
|
*/
|
|
202
|
-
static isAllowed$Ref<S extends object = JSONSchema>(
|
|
203
|
-
value: unknown,
|
|
204
|
-
options?: ParserOptions<S>,
|
|
205
|
-
allowPlainNameFragments = false,
|
|
206
|
-
) {
|
|
202
|
+
static isAllowed$Ref<S extends object = JSONSchema>(value: unknown, options?: ParserOptions<S>) {
|
|
207
203
|
if (this.is$Ref(value)) {
|
|
208
204
|
if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") {
|
|
209
205
|
// It's a JSON Pointer reference, which is always allowed
|
|
210
206
|
return true;
|
|
211
|
-
} else if (allowPlainNameFragments && value.$ref[0] === "#") {
|
|
212
|
-
// JSON Schema 2019-09+ allows plain-name fragments declared via $anchor/$dynamicAnchor
|
|
213
|
-
return true;
|
|
214
207
|
} else if (value.$ref[0] !== "#" && (!options || options.resolve?.external)) {
|
|
215
208
|
// It's an external reference, which is allowed by the options
|
|
216
209
|
return true;
|
|
@@ -293,12 +286,7 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
293
286
|
$ref: $Ref<S, O>,
|
|
294
287
|
resolvedValue: S,
|
|
295
288
|
options?: O,
|
|
296
|
-
useSpecCompliantRefSiblings = false,
|
|
297
289
|
): S {
|
|
298
|
-
if ($Ref.isExtended$Ref($ref) && useSpecCompliantRefSiblings) {
|
|
299
|
-
return preserveRefSiblings($ref, resolvedValue) as S;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
290
|
if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
|
|
303
291
|
const merged = {} as Partial<S>;
|
|
304
292
|
for (const key of Object.keys($ref)) {
|
|
@@ -337,27 +325,6 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
|
|
|
337
325
|
}
|
|
338
326
|
}
|
|
339
327
|
|
|
340
|
-
function preserveRefSiblings<T>(refValue: Record<string, any>, resolvedValue: T): T {
|
|
341
|
-
const preserved = {} as Record<string, any>;
|
|
342
|
-
const existingAllOf = "allOf" in refValue ? refValue.allOf : undefined;
|
|
343
|
-
|
|
344
|
-
for (const key of Object.keys(refValue)) {
|
|
345
|
-
if (key !== "$ref" && key !== "allOf") {
|
|
346
|
-
preserved[key] = refValue[key];
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const allOf = [resolvedValue];
|
|
351
|
-
if (Array.isArray(existingAllOf)) {
|
|
352
|
-
allOf.push(...existingAllOf);
|
|
353
|
-
} else if (existingAllOf !== undefined) {
|
|
354
|
-
allOf.push(existingAllOf);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
preserved.allOf = allOf;
|
|
358
|
-
return preserved as T;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
328
|
function deepMerge<T>(target: Partial<T>, source: Partial<T>): T {
|
|
362
329
|
//return {...target, ...source};
|
|
363
330
|
|
package/lib/refs.ts
CHANGED
|
@@ -112,7 +112,7 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
112
112
|
*/
|
|
113
113
|
_get$Ref(path: string) {
|
|
114
114
|
path = url.resolve(this._root$Ref.path!, path);
|
|
115
|
-
return this._getRef(
|
|
115
|
+
return this._getRef(path);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
@@ -149,15 +149,6 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
149
149
|
return $ref;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
_addExactAlias(path: string, targetPath: string) {
|
|
153
|
-
if (!path || this._exactAliases[path]) {
|
|
154
|
-
return this._exactAliases[path];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
this._exactAliases[path] = targetPath;
|
|
158
|
-
return targetPath;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
152
|
/**
|
|
162
153
|
* Resolves the given JSON reference.
|
|
163
154
|
*
|
|
@@ -169,14 +160,13 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
169
160
|
*/
|
|
170
161
|
_resolve(path: string, pathFromRoot: string, options?: O) {
|
|
171
162
|
const absPath = url.resolve(this._root$Ref.path!, path);
|
|
172
|
-
const
|
|
173
|
-
const $ref = this._getRef(canonicalPath);
|
|
163
|
+
const $ref = this._getRef(absPath);
|
|
174
164
|
|
|
175
165
|
if (!$ref) {
|
|
176
166
|
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
177
167
|
}
|
|
178
168
|
|
|
179
|
-
return $ref.resolve(
|
|
169
|
+
return $ref.resolve(absPath, options, path, pathFromRoot);
|
|
180
170
|
}
|
|
181
171
|
|
|
182
172
|
/**
|
|
@@ -189,8 +179,6 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
189
179
|
|
|
190
180
|
_aliases: $RefsMap<S, O> = {};
|
|
191
181
|
|
|
192
|
-
_exactAliases: Record<string, string> = {};
|
|
193
|
-
|
|
194
182
|
/**
|
|
195
183
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
196
184
|
*
|
|
@@ -209,7 +197,6 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
209
197
|
|
|
210
198
|
this._$refs = {};
|
|
211
199
|
this._aliases = {};
|
|
212
|
-
this._exactAliases = {};
|
|
213
200
|
|
|
214
201
|
// @ts-ignore
|
|
215
202
|
this._root$Ref = null;
|
package/lib/resolve-external.ts
CHANGED
|
@@ -29,11 +29,14 @@ function resolveExternal<S extends object = JSONSchema, O extends ParserOptions<
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
try {
|
|
32
|
+
const rootScopeBase = parser.$refs._root$Ref.dynamicIdScope
|
|
33
|
+
? getSchemaBasePath(parser.$refs._root$Ref.path!, parser.schema)
|
|
34
|
+
: parser.$refs._root$Ref.path!;
|
|
32
35
|
// console.log('Resolving $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
33
36
|
const promises = crawl(
|
|
34
37
|
parser.schema,
|
|
35
38
|
parser.$refs._root$Ref.path + "#",
|
|
36
|
-
|
|
39
|
+
rootScopeBase,
|
|
37
40
|
parser.$refs._root$Ref.dynamicIdScope,
|
|
38
41
|
parser.$refs,
|
|
39
42
|
options,
|
|
@@ -75,7 +78,7 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
75
78
|
|
|
76
79
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
|
|
77
80
|
seen.add(obj); // Track previously seen objects to avoid infinite recursion
|
|
78
|
-
const currentScopeBase =
|
|
81
|
+
const currentScopeBase = scopeBase;
|
|
79
82
|
if ($Ref.isExternal$Ref(obj)) {
|
|
80
83
|
promises.push(resolve$Ref<S, O>(obj, path, currentScopeBase, dynamicIdScope, $refs, options));
|
|
81
84
|
}
|
|
@@ -85,7 +88,9 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
|
|
|
85
88
|
const keyPath = Pointer.join(path, key);
|
|
86
89
|
const value = obj[key as keyof typeof obj] as string | JSONSchema | Buffer | undefined;
|
|
87
90
|
const childScopeBase =
|
|
88
|
-
dynamicIdScope &&
|
|
91
|
+
dynamicIdScope && value && typeof value === "object" && !ArrayBuffer.isView(value)
|
|
92
|
+
? getSchemaBasePath(currentScopeBase, value)
|
|
93
|
+
: currentScopeBase;
|
|
89
94
|
promises = promises.concat(crawl(value, keyPath, childScopeBase, dynamicIdScope, $refs, options, seen, external));
|
|
90
95
|
}
|
|
91
96
|
}
|
|
@@ -38,7 +38,7 @@ export function registerSchemaResources<S extends object = JSONSchema, O extends
|
|
|
38
38
|
|
|
39
39
|
const seen = new Set<object>();
|
|
40
40
|
|
|
41
|
-
const visit = (node: unknown, scopeBase: string
|
|
41
|
+
const visit = (node: unknown, scopeBase: string) => {
|
|
42
42
|
if (!node || typeof node !== "object" || ArrayBuffer.isView(node) || seen.has(node)) {
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
@@ -46,18 +46,16 @@ export function registerSchemaResources<S extends object = JSONSchema, O extends
|
|
|
46
46
|
seen.add(node);
|
|
47
47
|
|
|
48
48
|
const nextScopeBase = getSchemaBasePath(scopeBase, node);
|
|
49
|
-
const resourcePointerTokens = nextScopeBase === scopeBase ? pointerTokens : [];
|
|
50
49
|
if (nextScopeBase !== scopeBase) {
|
|
51
50
|
$refs._addAlias(nextScopeBase, node as S, pathType, dynamicIdScope);
|
|
52
51
|
}
|
|
53
|
-
registerAnchorAliases($refs, nextScopeBase, resourcePointerTokens, node);
|
|
54
52
|
|
|
55
53
|
for (const key of Object.keys(node)) {
|
|
56
|
-
visit((node as Record<string, unknown>)[key], nextScopeBase
|
|
54
|
+
visit((node as Record<string, unknown>)[key], nextScopeBase);
|
|
57
55
|
}
|
|
58
56
|
};
|
|
59
57
|
|
|
60
|
-
visit(value, basePath
|
|
58
|
+
visit(value, basePath);
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
function getSchemaId(value: unknown): string | undefined {
|
|
@@ -73,37 +71,3 @@ function getSchemaId(value: unknown): string | undefined {
|
|
|
73
71
|
|
|
74
72
|
return undefined;
|
|
75
73
|
}
|
|
76
|
-
|
|
77
|
-
function registerAnchorAliases<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
|
|
78
|
-
$refs: $Refs<S, O>,
|
|
79
|
-
scopeBase: string,
|
|
80
|
-
pointerTokens: string[],
|
|
81
|
-
value: unknown,
|
|
82
|
-
) {
|
|
83
|
-
if (!value || typeof value !== "object" || ArrayBuffer.isView(value)) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const resourceBase = url.stripHash(scopeBase);
|
|
88
|
-
const targetPath = pointerTokens.length > 0 ? joinPointerPath(resourceBase, pointerTokens) : `${resourceBase}#`;
|
|
89
|
-
const anchors = [
|
|
90
|
-
(value as { $anchor?: unknown }).$anchor,
|
|
91
|
-
(value as { $dynamicAnchor?: unknown }).$dynamicAnchor,
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
for (const anchor of anchors) {
|
|
95
|
-
if (typeof anchor === "string" && anchor.length > 0) {
|
|
96
|
-
$refs._addExactAlias(`${resourceBase}#${anchor}`, targetPath);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function joinPointerPath(basePath: string, tokens: string[]) {
|
|
102
|
-
let path = `${basePath}#`;
|
|
103
|
-
|
|
104
|
-
for (const token of tokens) {
|
|
105
|
-
path += `/${token.replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return path;
|
|
109
|
-
}
|
package/lib/util/url.ts
CHANGED
|
@@ -7,7 +7,7 @@ const jsonPointerTilde = /~0/g;
|
|
|
7
7
|
|
|
8
8
|
import { isWindows } from "./is-windows.js";
|
|
9
9
|
|
|
10
|
-
const isAbsoluteWin32Path = /^[a-zA-Z]
|
|
10
|
+
const isAbsoluteWin32Path = /^[a-zA-Z]:[\\/]/;
|
|
11
11
|
|
|
12
12
|
// RegExp patterns to URL-encode special characters in local filesystem paths
|
|
13
13
|
const urlEncodePatterns = [
|
|
@@ -426,6 +426,10 @@ export function fromFileSystemPath(path: string) {
|
|
|
426
426
|
* Converts a URL to a local filesystem path.
|
|
427
427
|
*/
|
|
428
428
|
export function toFileSystemPath(path: string | undefined, keepFileProtocol?: boolean): string {
|
|
429
|
+
// Bare "%" characters are valid in filesystem paths, but they make `decodeURI` throw.
|
|
430
|
+
// Escape only the non-encoded ones so percent-encoded sequences still decode normally.
|
|
431
|
+
path = path!.replace(/%(?![0-9A-Fa-f]{2})/g, "%25");
|
|
432
|
+
|
|
429
433
|
// Step 1: `decodeURI` will decode characters such as Cyrillic characters, spaces, etc.
|
|
430
434
|
path = decodeURI(path!);
|
|
431
435
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apidevtools/json-schema-ref-parser",
|
|
3
|
-
"version": "15.3.
|
|
3
|
+
"version": "15.3.5",
|
|
4
4
|
"description": "Parse, Resolve, and Dereference JSON Schema $ref pointers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/lib/index.d.ts",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"test": "vitest --coverage",
|
|
16
16
|
"test:specific": "vitest invalid",
|
|
17
17
|
"test:node": "yarn test",
|
|
18
|
-
"test:browser": "
|
|
18
|
+
"test:browser": "node ./test/fixtures/run-browser-tests.mjs",
|
|
19
19
|
"test:update": "vitest -u",
|
|
20
20
|
"test:watch": "vitest -w"
|
|
21
21
|
},
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@types/node": "^25",
|
|
84
84
|
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
85
85
|
"@typescript-eslint/parser": "^8.57.2",
|
|
86
|
-
"@vitest/coverage-v8": "^4.1.
|
|
86
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
87
87
|
"cross-env": "^10.1.0",
|
|
88
88
|
"eslint": "^10.1.0",
|
|
89
89
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"rimraf": "^6.1.3",
|
|
98
98
|
"typescript": "^6.0.2",
|
|
99
99
|
"typescript-eslint": "^8.57.2",
|
|
100
|
-
"vitest": "^4.1.
|
|
100
|
+
"vitest": "^4.1.2"
|
|
101
101
|
},
|
|
102
102
|
"release": {
|
|
103
103
|
"branches": [
|