From e1c341793b1bdd77457df9b2115a6091d0fa5fd5 Mon Sep 17 00:00:00 2001 From: jeromeh Date: Tue, 20 Oct 2020 19:51:36 +0200 Subject: [PATCH 1/2] add processedObject list to avoid recursion in already processed tree parts add dereferencedCache to avoid to re-treat object already dereferenced --- lib/dereference.js | 108 ++++++++++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/lib/dereference.js b/lib/dereference.js index 4eeb82dd..66bcf625 100644 --- a/lib/dereference.js +++ b/lib/dereference.js @@ -16,7 +16,7 @@ module.exports = dereference; */ function dereference (parser, options) { // console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path); - let dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", [], parser.$refs, options); + let dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", [], [], {}, parser.$refs, options); parser.$refs.circular = dereferenced.circular; parser.schema = dereferenced.value; } @@ -28,43 +28,38 @@ function dereference (parser, options) { * @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash * @param {string} pathFromRoot - The path of `obj` from the schema root * @param {object[]} parents - An array of the parent objects that have already been dereferenced + * @param {object[]} processedObjects - An array of all the objects that have already been processed + * @param {object} dereferencedCache - An map of all the dereferenced objects * @param {$Refs} $refs * @param {$RefParserOptions} options * @returns {{value: object, circular: boolean}} */ -function crawl (obj, path, pathFromRoot, parents, $refs, options) { +function crawl (obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options) { let dereferenced; let result = { value: obj, circular: false }; - if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) { - parents.push(obj); + if (options.dereference.circular === "ignore" || processedObjects.indexOf(obj) === -1) { + if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) { + parents.push(obj); + processedObjects.push(obj); - if ($Ref.isAllowed$Ref(obj, options)) { - dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, $refs, options); - result.circular = dereferenced.circular; - result.value = dereferenced.value; - } - else { - for (let key of Object.keys(obj)) { - let keyPath = Pointer.join(path, key); - let keyPathFromRoot = Pointer.join(pathFromRoot, key); - let value = obj[key]; - let circular = false; - - if ($Ref.isAllowed$Ref(value, options)) { - dereferenced = dereference$Ref(value, keyPath, keyPathFromRoot, parents, $refs, options); - circular = dereferenced.circular; - // Avoid pointless mutations; breaks frozen objects to no profit - if (obj[key] !== dereferenced.value) { - obj[key] = dereferenced.value; - } - } - else { - if (parents.indexOf(value) === -1) { - dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, $refs, options); + if ($Ref.isAllowed$Ref(obj, options)) { + dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options); + result.circular = dereferenced.circular; + result.value = dereferenced.value; + } + else { + for (let key of Object.keys(obj)) { + let keyPath = Pointer.join(path, key); + let keyPathFromRoot = Pointer.join(pathFromRoot, key); + let value = obj[key]; + let circular = false; + + if ($Ref.isAllowed$Ref(value, options)) { + dereferenced = dereference$Ref(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options); circular = dereferenced.circular; // Avoid pointless mutations; breaks frozen objects to no profit if (obj[key] !== dereferenced.value) { @@ -72,16 +67,26 @@ function crawl (obj, path, pathFromRoot, parents, $refs, options) { } } else { - circular = foundCircularReference(keyPath, $refs, options); + if (parents.indexOf(value) === -1) { + dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options); + circular = dereferenced.circular; + // Avoid pointless mutations; breaks frozen objects to no profit + if (obj[key] !== dereferenced.value) { + obj[key] = dereferenced.value; + } + } + else { + circular = foundCircularReference(keyPath, $refs, options); + } } - } - // Set the "isCircular" flag if this or any other property is circular - result.circular = result.circular || circular; + // Set the "isCircular" flag if this or any other property is circular + result.circular = result.circular || circular; + } } - } - parents.pop(); + parents.pop(); + } } return result; @@ -94,14 +99,37 @@ function crawl (obj, path, pathFromRoot, parents, $refs, options) { * @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash * @param {string} pathFromRoot - The path of `$ref` from the schema root * @param {object[]} parents - An array of the parent objects that have already been dereferenced + * @param {object[]} processedObjects - An array of all the objects that have already been dereferenced * @param {$Refs} $refs * @param {$RefParserOptions} options * @returns {{value: object, circular: boolean}} */ -function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) { +function dereference$Ref ($ref, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options) { // console.log('Dereferencing $ref pointer "%s" at %s', $ref.$ref, path); let $refPath = url.resolve(path, $ref.$ref); + + if (dereferencedCache[$refPath]) { + const cache = dereferencedCache[$refPath]; + + const refKeys = Object.keys($ref); + if (refKeys.length > 1) { + const extraKeys = {}; + for (let key of refKeys) { + if (key !== "$ref" && !(key in cache.value)) { + extraKeys[key] = $ref[key]; + } + } + return { + circular: cache.circular, + value: Object.assign({}, cache.value, extraKeys), + }; + } + + return cache; + } + + let pointer = $refs._resolve($refPath, path, options); if (pointer === null) { @@ -122,7 +150,7 @@ function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) { // Crawl the dereferenced value (unless it's circular) if (!circular) { // Determine if the dereferenced value is circular - let dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, $refs, options); + let dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options); circular = dereferenced.circular; dereferencedValue = dereferenced.value; } @@ -138,10 +166,18 @@ function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) { dereferencedValue.$ref = pathFromRoot; } - return { + + const dereferencedObject = { circular, value: dereferencedValue }; + + // only cache if no extra properties than $ref + if (Object.keys($ref).length === 1) { + dereferencedCache[$refPath] = dereferencedObject; + } + + return dereferencedObject; } /** From 991e1a0cdb6be4117e41dea7d1b989a2c3143f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20HENAFF?= Date: Tue, 3 Nov 2020 18:31:45 +0100 Subject: [PATCH 2/2] missing jsdoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Rożek --- lib/dereference.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dereference.js b/lib/dereference.js index 66bcf625..ca338f25 100644 --- a/lib/dereference.js +++ b/lib/dereference.js @@ -100,6 +100,7 @@ function crawl (obj, path, pathFromRoot, parents, processedObjects, dereferenced * @param {string} pathFromRoot - The path of `$ref` from the schema root * @param {object[]} parents - An array of the parent objects that have already been dereferenced * @param {object[]} processedObjects - An array of all the objects that have already been dereferenced + * @param {object} dereferencedCache - An map of all the dereferenced objects * @param {$Refs} $refs * @param {$RefParserOptions} options * @returns {{value: object, circular: boolean}}