@@ -16,7 +16,7 @@ module.exports = dereference;
1616 */
1717function dereference ( parser , options ) {
1818 // console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
19- let dereferenced = crawl ( parser . schema , parser . $refs . _root$Ref . path , "#" , [ ] , parser . $refs , options ) ;
19+ let dereferenced = crawl ( parser . schema , parser . $refs . _root$Ref . path , "#" , [ ] , [ ] , { } , parser . $refs , options ) ;
2020 parser . $refs . circular = dereferenced . circular ;
2121 parser . schema = dereferenced . value ;
2222}
@@ -28,60 +28,65 @@ function dereference (parser, options) {
2828 * @param {string } path - The full path of `obj`, possibly with a JSON Pointer in the hash
2929 * @param {string } pathFromRoot - The path of `obj` from the schema root
3030 * @param {object[] } parents - An array of the parent objects that have already been dereferenced
31+ * @param {object[] } processedObjects - An array of all the objects that have already been processed
32+ * @param {object } dereferencedCache - An map of all the dereferenced objects
3133 * @param {$Refs } $refs
3234 * @param {$RefParserOptions } options
3335 * @returns {{value: object, circular: boolean} }
3436 */
35- function crawl ( obj , path , pathFromRoot , parents , $refs , options ) {
37+ function crawl ( obj , path , pathFromRoot , parents , processedObjects , dereferencedCache , $refs , options ) {
3638 let dereferenced ;
3739 let result = {
3840 value : obj ,
3941 circular : false
4042 } ;
4143
42- if ( obj && typeof obj === "object" && ! ArrayBuffer . isView ( obj ) ) {
43- parents . push ( obj ) ;
44+ if ( options . dereference . circular === "ignore" || processedObjects . indexOf ( obj ) === - 1 ) {
45+ if ( obj && typeof obj === "object" && ! ArrayBuffer . isView ( obj ) ) {
46+ parents . push ( obj ) ;
47+ processedObjects . push ( obj ) ;
4448
45- if ( $Ref . isAllowed$Ref ( obj , options ) ) {
46- dereferenced = dereference$Ref ( obj , path , pathFromRoot , parents , $refs , options ) ;
47- result . circular = dereferenced . circular ;
48- result . value = dereferenced . value ;
49- }
50- else {
51- for ( let key of Object . keys ( obj ) ) {
52- let keyPath = Pointer . join ( path , key ) ;
53- let keyPathFromRoot = Pointer . join ( pathFromRoot , key ) ;
54- let value = obj [ key ] ;
55- let circular = false ;
56-
57- if ( $Ref . isAllowed$Ref ( value , options ) ) {
58- dereferenced = dereference$Ref ( value , keyPath , keyPathFromRoot , parents , $refs , options ) ;
59- circular = dereferenced . circular ;
60- // Avoid pointless mutations; breaks frozen objects to no profit
61- if ( obj [ key ] !== dereferenced . value ) {
62- obj [ key ] = dereferenced . value ;
63- }
64- }
65- else {
66- if ( parents . indexOf ( value ) === - 1 ) {
67- dereferenced = crawl ( value , keyPath , keyPathFromRoot , parents , $refs , options ) ;
49+ if ( $Ref . isAllowed$Ref ( obj , options ) ) {
50+ dereferenced = dereference$Ref ( obj , path , pathFromRoot , parents , processedObjects , dereferencedCache , $refs , options ) ;
51+ result . circular = dereferenced . circular ;
52+ result . value = dereferenced . value ;
53+ }
54+ else {
55+ for ( let key of Object . keys ( obj ) ) {
56+ let keyPath = Pointer . join ( path , key ) ;
57+ let keyPathFromRoot = Pointer . join ( pathFromRoot , key ) ;
58+ let value = obj [ key ] ;
59+ let circular = false ;
60+
61+ if ( $Ref . isAllowed$Ref ( value , options ) ) {
62+ dereferenced = dereference$Ref ( value , keyPath , keyPathFromRoot , parents , processedObjects , dereferencedCache , $refs , options ) ;
6863 circular = dereferenced . circular ;
6964 // Avoid pointless mutations; breaks frozen objects to no profit
7065 if ( obj [ key ] !== dereferenced . value ) {
7166 obj [ key ] = dereferenced . value ;
7267 }
7368 }
7469 else {
75- circular = foundCircularReference ( keyPath , $refs , options ) ;
70+ if ( parents . indexOf ( value ) === - 1 ) {
71+ dereferenced = crawl ( value , keyPath , keyPathFromRoot , parents , processedObjects , dereferencedCache , $refs , options ) ;
72+ circular = dereferenced . circular ;
73+ // Avoid pointless mutations; breaks frozen objects to no profit
74+ if ( obj [ key ] !== dereferenced . value ) {
75+ obj [ key ] = dereferenced . value ;
76+ }
77+ }
78+ else {
79+ circular = foundCircularReference ( keyPath , $refs , options ) ;
80+ }
7681 }
77- }
7882
79- // Set the "isCircular" flag if this or any other property is circular
80- result . circular = result . circular || circular ;
83+ // Set the "isCircular" flag if this or any other property is circular
84+ result . circular = result . circular || circular ;
85+ }
8186 }
82- }
8387
84- parents . pop ( ) ;
88+ parents . pop ( ) ;
89+ }
8590 }
8691
8792 return result ;
@@ -94,14 +99,37 @@ function crawl (obj, path, pathFromRoot, parents, $refs, options) {
9499 * @param {string } path - The full path of `$ref`, possibly with a JSON Pointer in the hash
95100 * @param {string } pathFromRoot - The path of `$ref` from the schema root
96101 * @param {object[] } parents - An array of the parent objects that have already been dereferenced
102+ * @param {object[] } processedObjects - An array of all the objects that have already been dereferenced
97103 * @param {$Refs } $refs
98104 * @param {$RefParserOptions } options
99105 * @returns {{value: object, circular: boolean} }
100106 */
101- function dereference$Ref ( $ref , path , pathFromRoot , parents , $refs , options ) {
107+ function dereference$Ref ( $ref , path , pathFromRoot , parents , processedObjects , dereferencedCache , $refs , options ) {
102108 // console.log('Dereferencing $ref pointer "%s" at %s', $ref.$ref, path);
103109
104110 let $refPath = url . resolve ( path , $ref . $ref ) ;
111+
112+ if ( dereferencedCache [ $refPath ] ) {
113+ const cache = dereferencedCache [ $refPath ] ;
114+
115+ const refKeys = Object . keys ( $ref ) ;
116+ if ( refKeys . length > 1 ) {
117+ const extraKeys = { } ;
118+ for ( let key of refKeys ) {
119+ if ( key !== "$ref" && ! ( key in cache . value ) ) {
120+ extraKeys [ key ] = $ref [ key ] ;
121+ }
122+ }
123+ return {
124+ circular : cache . circular ,
125+ value : Object . assign ( { } , cache . value , extraKeys ) ,
126+ } ;
127+ }
128+
129+ return cache ;
130+ }
131+
132+
105133 let pointer = $refs . _resolve ( $refPath , path , options ) ;
106134
107135 if ( pointer === null ) {
@@ -122,7 +150,7 @@ function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) {
122150 // Crawl the dereferenced value (unless it's circular)
123151 if ( ! circular ) {
124152 // Determine if the dereferenced value is circular
125- let dereferenced = crawl ( dereferencedValue , pointer . path , pathFromRoot , parents , $refs , options ) ;
153+ let dereferenced = crawl ( dereferencedValue , pointer . path , pathFromRoot , parents , processedObjects , dereferencedCache , $refs , options ) ;
126154 circular = dereferenced . circular ;
127155 dereferencedValue = dereferenced . value ;
128156 }
@@ -138,10 +166,18 @@ function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) {
138166 dereferencedValue . $ref = pathFromRoot ;
139167 }
140168
141- return {
169+
170+ const dereferencedObject = {
142171 circular,
143172 value : dereferencedValue
144173 } ;
174+
175+ // only cache if no extra properties than $ref
176+ if ( Object . keys ( $ref ) . length === 1 ) {
177+ dereferencedCache [ $refPath ] = dereferencedObject ;
178+ }
179+
180+ return dereferencedObject ;
145181}
146182
147183/**
0 commit comments