@@ -22,171 +22,170 @@ module.exports = bundle;
22
22
* @param {$RefParserOptions } options
23
23
*/
24
24
function bundle ( parser , options ) {
25
- util . debug ( 'Bundling $ref pointers in %s' , parser . _basePath ) ;
25
+ util . debug ( 'Bundling $ref pointers in %s' , parser . $refs . _basePath ) ;
26
26
27
- optimize ( parser . $refs ) ;
28
- remap ( parser . $refs , options ) ;
29
- dereference ( parser . _basePath , parser . $refs , options ) ;
30
- }
27
+ // Build an inventory of all $ref pointers in the JSON Schema
28
+ var inventory = [ ] ;
29
+ crawl ( parser . schema , parser . $refs . _basePath + '#' , '#' , inventory , parser . $refs , options ) ;
31
30
32
- /**
33
- * Optimizes the {@link $Ref#referencedAt} list for each {@link $Ref} to contain as few entries
34
- * as possible (ideally, one).
35
- *
36
- * @example :
37
- * {
38
- * first: { $ref: somefile.json#/some/part },
39
- * second: { $ref: somefile.json#/another/part },
40
- * third: { $ref: somefile.json },
41
- * fourth: { $ref: somefile.json#/some/part/sub/part }
42
- * }
43
- *
44
- * In this example, there are four references to the same file, but since the third reference points
45
- * to the ENTIRE file, that's the only one we care about. The other three can just be remapped to point
46
- * inside the third one.
47
- *
48
- * On the other hand, if the third reference DIDN'T exist, then the first and second would both be
49
- * significant, since they point to different parts of the file. The fourth reference is not significant,
50
- * since it can still be remapped to point inside the first one.
51
- *
52
- * @param {$Refs } $refs
53
- */
54
- function optimize ( $refs ) {
55
- Object . keys ( $refs . _$refs ) . forEach ( function ( key ) {
56
- var $ref = $refs . _$refs [ key ] ;
57
-
58
- // Find the first reference to this $ref
59
- var first = $ref . referencedAt . filter ( function ( at ) { return at . firstReference ; } ) [ 0 ] ;
60
-
61
- // Do any of the references point to the entire file?
62
- var entireFile = $ref . referencedAt . filter ( function ( at ) { return at . hash === '#' ; } ) ;
63
- if ( entireFile . length === 1 ) {
64
- // We found a single reference to the entire file. Done!
65
- $ref . referencedAt = entireFile ;
66
- }
67
- else if ( entireFile . length > 1 ) {
68
- // We found more than one reference to the entire file. Pick the first one.
69
- if ( entireFile . indexOf ( first ) >= 0 ) {
70
- $ref . referencedAt = [ first ] ;
71
- }
72
- else {
73
- $ref . referencedAt = entireFile . slice ( 0 , 1 ) ;
74
- }
75
- }
76
- else {
77
- // There are noo references to the entire file, so optimize the list of reference points
78
- // by eliminating any duplicate/redundant ones (e.g. "fourth" in the example above)
79
- console . log ( '========================= %s BEFORE =======================' , $ref . path , JSON . stringify ( $ref . referencedAt , null , 2 ) ) ;
80
- [ first ] . concat ( $ref . referencedAt ) . forEach ( function ( at ) {
81
- dedupe ( at , $ref . referencedAt ) ;
82
- } ) ;
83
- console . log ( '========================= %s AFTER =======================' , $ref . path , JSON . stringify ( $ref . referencedAt , null , 2 ) ) ;
84
- }
85
- } ) ;
86
- }
87
-
88
- /**
89
- * Removes redundant entries from the {@link $Ref#referencedAt} list.
90
- *
91
- * @param {object } original - The {@link $Ref#referencedAt} entry to keep
92
- * @param {object[] } dupes - The {@link $Ref#referencedAt} list to dedupe
93
- */
94
- function dedupe ( original , dupes ) {
95
- for ( var i = dupes . length - 1 ; i >= 0 ; i -- ) {
96
- var dupe = dupes [ i ] ;
97
- if ( dupe !== original && dupe . hash . indexOf ( original . hash ) === 0 ) {
98
- dupes . splice ( i , 1 ) ;
99
- }
100
- }
31
+ // Remap all $ref pointers
32
+ remap ( inventory ) ;
101
33
}
102
34
103
35
/**
104
- * Re-maps all $ref pointers in the schema, so that they are relative to the root of the schema .
36
+ * Recursively crawls the given value, and inventories all JSON references .
105
37
*
38
+ * @param {* } obj - The value to crawl. If it's not an object or array, it will be ignored.
39
+ * @param {string } path - The full path of `obj`, possibly with a JSON Pointer in the hash
40
+ * @param {string } pathFromRoot - The path of `obj` from the schema root
41
+ * @param {object[] } inventory - An array of already-inventoried $ref pointers
106
42
* @param {$Refs } $refs
107
43
* @param {$RefParserOptions } options
108
44
*/
109
- function remap ( $refs , options ) {
110
- var remapped = [ ] ;
111
-
112
- // Crawl the schema and determine the re-mapped values for all $ref pointers.
113
- // NOTE: We don't actually APPLY the re-mappings yet, since that can affect other re-mappings
114
- Object . keys ( $refs . _$refs ) . forEach ( function ( key ) {
115
- var $ref = $refs . _$refs [ key ] ;
116
- crawl ( $ref . value , $ref . path + '#' , $refs , remapped , options ) ;
117
- } ) ;
45
+ function crawl ( obj , path , pathFromRoot , inventory , $refs , options ) {
46
+ if ( obj && typeof obj === 'object' ) {
47
+ var keys = Object . keys ( obj ) ;
118
48
119
- // Now APPLY all of the re-mappings
120
- for ( var i = 0 ; i < remapped . length ; i ++ ) {
121
- var mapping = remapped [ i ] ;
122
- mapping . old$Ref . $ref = mapping . new$Ref . $ref ;
123
- }
124
- }
49
+ // Most people will expect references to be bundled into the the "definitions" property,
50
+ // so we always crawl that property first, if it exists.
51
+ var defs = keys . indexOf ( 'definitions' ) ;
52
+ if ( defs > 0 ) {
53
+ keys . splice ( 0 , 0 , keys . splice ( defs , 1 ) [ 0 ] ) ;
54
+ }
125
55
126
- /**
127
- * Recursively crawls the given value, and re-maps any JSON references.
128
- *
129
- * @param {* } obj - The value to crawl. If it's not an object or array, it will be ignored.
130
- * @param {string } path - The path to use for resolving relative JSON references
131
- * @param {$Refs } $refs - The resolved JSON references
132
- * @param {object[] } remapped - An array of the re-mapped JSON references
133
- * @param {$RefParserOptions } options
134
- */
135
- function crawl ( obj , path , $refs , remapped , options ) {
136
- if ( obj && typeof obj === 'object' ) {
137
- Object . keys ( obj ) . forEach ( function ( key ) {
56
+ keys . forEach ( function ( key ) {
138
57
var keyPath = Pointer . join ( path , key ) ;
58
+ var keyPathFromRoot = Pointer . join ( pathFromRoot , key ) ;
139
59
var value = obj [ key ] ;
140
60
141
61
if ( $Ref . is$Ref ( value ) ) {
142
- // We found a $ref, so resolve it
143
- util . debug ( 'Re-mapping $ref pointer "%s" at %s' , value . $ref , keyPath ) ;
144
- var $refPath = url . resolve ( path , value . $ref ) ;
145
- var pointer = $refs . _resolve ( $refPath , options ) ;
146
-
147
- // Find the path from the root of the JSON schema
148
- var hash = util . path . getHash ( value . $ref ) ;
149
- var referencedAt = pointer . $ref . referencedAt . filter ( function ( at ) {
150
- return hash . indexOf ( at . hash ) === 0 ;
151
- } ) [ 0 ] ;
152
-
153
- console . log (
154
- 'referencedAt.pathFromRoot =' , referencedAt . pathFromRoot ,
155
- '\nreferencedAt.hash =' , referencedAt . hash ,
156
- '\nhash =' , hash ,
157
- '\npointer.path.hash =' , util . path . getHash ( pointer . path )
158
- ) ;
159
-
160
- // Re-map the value
161
- var new$RefPath = referencedAt . pathFromRoot + util . path . getHash ( pointer . path ) . substr ( 1 ) ;
162
- util . debug ( ' new value: %s' , new$RefPath ) ;
163
- remapped . push ( {
164
- old$Ref : value ,
165
- new$Ref : { $ref : new$RefPath } // Note: DON'T name this property `new` (https://github.com/BigstickCarpet/json-schema-ref-parser/issues/3)
166
- } ) ;
62
+ // Skip this $ref if we've already inventoried it
63
+ if ( ! inventory . some ( function ( i ) { return i . parent === obj && i . key === key ; } ) ) {
64
+ inventory$Ref ( obj , key , path , keyPathFromRoot , inventory , $refs , options ) ;
65
+ }
167
66
}
168
67
else {
169
- crawl ( value , keyPath , $refs , remapped , options ) ;
68
+ crawl ( value , keyPath , keyPathFromRoot , inventory , $refs , options ) ;
170
69
}
171
70
} ) ;
172
71
}
173
72
}
174
73
175
74
/**
176
- * Dereferences each external $ref pointer exactly ONCE.
75
+ * Inventories the given JSON Reference (i.e. records detailed information about it so we can
76
+ * optimize all $refs in the schema), and then crawls the resolved value.
177
77
*
178
- * @param {string } basePath
78
+ * @param {object } $refParent - The object that contains a JSON Reference as one of its keys
79
+ * @param {string } $refKey - The key in `$refParent` that is a JSON Reference
80
+ * @param {string } path - The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
81
+ * @param {string } pathFromRoot - The path of the JSON Reference at `$refKey`, from the schema root
82
+ * @param {object[] } inventory - An array of already-inventoried $ref pointers
179
83
* @param {$Refs } $refs
180
84
* @param {$RefParserOptions } options
181
85
*/
182
- function dereference ( basePath , $refs , options ) {
183
- basePath = util . path . stripHash ( basePath ) ;
86
+ function inventory$Ref ( $refParent , $refKey , path , pathFromRoot , inventory , $refs , options ) {
87
+ var $ref = $refParent [ $refKey ] ;
88
+ var $refPath = url . resolve ( path , $ref . $ref ) ;
89
+ var pointer = $refs . _resolve ( $refPath , options ) ;
90
+ var depth = Pointer . parse ( pathFromRoot ) . length ;
91
+ var file = util . path . stripHash ( pointer . path ) ;
92
+ var hash = util . path . getHash ( pointer . path ) ;
93
+ var external = file !== $refs . _basePath ;
94
+ var extended = Object . keys ( $ref ) . length > 1 ;
95
+
96
+ inventory . push ( {
97
+ $ref : $ref , // The JSON Reference (e.g. {$ref: string})
98
+ parent : $refParent , // The object that contains this $ref pointer
99
+ key : $refKey , // The key in `parent` that is the $ref pointer
100
+ pathFromRoot : pathFromRoot , // The path to the $ref pointer, from the JSON Schema root
101
+ depth : depth , // How far from the JSON Schema root is this $ref pointer?
102
+ file : file , // The file that the $ref pointer resolves to
103
+ hash : hash , // The hash within `file` that the $ref pointer resolves to
104
+ value : pointer . value , // The resolved value of the $ref pointer
105
+ circular : pointer . circular , // Is this $ref pointer DIRECTLY circular? (i.e. it references itself)
106
+ extended : extended , // Does this $ref extend its resolved value? (i.e. it has extra properties, in addition to "$ref")
107
+ external : external // Does this $ref pointer point to a file other than the main JSON Schema file?
108
+ } ) ;
184
109
185
- Object . keys ( $refs . _$refs ) . forEach ( function ( key ) {
186
- var $ref = $refs . _$refs [ key ] ;
110
+ // Recursively crawl the resolved value
111
+ crawl ( pointer . value , pointer . path , pathFromRoot , inventory , $refs , options ) ;
112
+ }
187
113
188
- if ( $ref . referencedAt . length > 0 ) {
189
- $refs . set ( basePath + $ref . referencedAt [ 0 ] . pathFromRoot , $ref . value , options ) ;
114
+ /**
115
+ * Re-maps every $ref pointer, so that they're all relative to the root of the JSON Schema.
116
+ * Each referenced value is dereferenced EXACTLY ONCE. All subsequent references to the same
117
+ * value are re-mapped to point to the first reference.
118
+ *
119
+ * @example :
120
+ * {
121
+ * first: { $ref: somefile.json#/some/part },
122
+ * second: { $ref: somefile.json#/another/part },
123
+ * third: { $ref: somefile.json },
124
+ * fourth: { $ref: somefile.json#/some/part/sub/part }
125
+ * }
126
+ *
127
+ * In this example, there are four references to the same file, but since the third reference points
128
+ * to the ENTIRE file, that's the only one we need to dereference. The other three can just be
129
+ * remapped to point inside the third one.
130
+ *
131
+ * On the other hand, if the third reference DIDN'T exist, then the first and second would both need
132
+ * to be dereferenced, since they point to different parts of the file. The fourth reference does NOT
133
+ * need to be dereferenced, because it can be remapped to point inside the first one.
134
+ *
135
+ * @param {object[] } inventory
136
+ */
137
+ function remap ( inventory ) {
138
+ // Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them
139
+ inventory . sort ( function ( a , b ) {
140
+ if ( a . file !== b . file ) {
141
+ return a . file < b . file ? - 1 : + 1 ; // Group all the $refs that point to the same file
142
+ }
143
+ else if ( a . hash !== b . hash ) {
144
+ return a . hash < b . hash ? - 1 : + 1 ; // Group all the $refs that point to the same part of the file
145
+ }
146
+ else if ( a . circular !== b . circular ) {
147
+ return a . circular ? - 1 : + 1 ; // If the $ref points to itself, then sort it higher than other $refs that point to this $ref
148
+ }
149
+ else if ( a . extended !== b . extended ) {
150
+ return a . extended ? + 1 : - 1 ; // If the $ref extends the resolved value, then sort it lower than other $refs that don't extend the value
151
+ }
152
+ else if ( a . depth !== b . depth ) {
153
+ return a . depth - b . depth ; // Sort $refs by how close they are to the JSON Schema root
154
+ }
155
+ else {
156
+ // If all else is equal, use the $ref that's in the "definitions" property
157
+ return b . pathFromRoot . lastIndexOf ( '/definitions' ) - a . pathFromRoot . lastIndexOf ( '/definitions' ) ;
190
158
}
191
159
} ) ;
160
+
161
+ var file , hash , pathFromRoot ;
162
+ inventory . forEach ( function ( i ) {
163
+ util . debug ( 'Re-mapping $ref pointer "%s" at %s' , i . $ref . $ref , i . pathFromRoot ) ;
164
+
165
+ if ( ! i . external ) {
166
+ // This $ref already resolves to the main JSON Schema file
167
+ i . $ref . $ref = i . hash ;
168
+ }
169
+ else if ( i . file !== file || i . hash . indexOf ( hash ) !== 0 ) {
170
+ // We've moved to a new file or new hash
171
+ file = i . file ;
172
+ hash = i . hash ;
173
+ pathFromRoot = i . pathFromRoot ;
174
+
175
+ // This is the first $ref to point to this value, so dereference the value.
176
+ // Any other $refs that point to the same value will point to this $ref instead
177
+ i . $ref = i . parent [ i . key ] = util . dereference ( i . $ref , i . value ) ;
178
+
179
+ if ( i . circular ) {
180
+ // This $ref points to itself
181
+ i . $ref . $ref = i . pathFromRoot ;
182
+ }
183
+ }
184
+ else {
185
+ // This $ref points to the same value as the prevous $ref
186
+ i . $ref . $ref = Pointer . join ( pathFromRoot , Pointer . parse ( i . hash ) ) ;
187
+ }
188
+
189
+ util . debug ( ' new value: %s' , ( i . $ref && i . $ref . $ref ) ? i . $ref . $ref : '[object Object]' ) ;
190
+ } ) ;
192
191
}
0 commit comments