@@ -4,16 +4,19 @@ import { findKey, includes, isPlainObject, map } from 'lodash'
4
4
import { typeOfSchema } from './typeOfSchema'
5
5
import { AST , hasStandaloneName , T_ANY , T_ANY_ADDITIONAL_PROPERTIES , TInterface , TInterfaceParam , TNamedInterface } from './types/AST'
6
6
import { JSONSchema , JSONSchemaWithDefinitions , SchemaSchema } from './types/JSONSchema'
7
- import { error , log } from './utils'
7
+ import { error , generateName , log } from './utils'
8
8
9
9
export type Processed = Map < JSONSchema | JSONSchema4Type , AST >
10
10
11
+ export type UsedNames = Set < string >
12
+
11
13
export function parse (
12
14
schema : JSONSchema | JSONSchema4Type ,
13
15
rootSchema = schema as JSONSchema ,
14
16
keyName ?: string ,
15
17
isSchema = true ,
16
- processed : Processed = new Map < JSONSchema | JSONSchema4Type , AST > ( )
18
+ processed : Processed = new Map < JSONSchema | JSONSchema4Type , AST > ( ) ,
19
+ usedNames = new Set < string > ( )
17
20
) : AST {
18
21
19
22
// If we've seen this node before, return it.
@@ -32,7 +35,7 @@ export function parse(
32
35
const set = ( _ast : AST ) => Object . assign ( ast , _ast )
33
36
34
37
return isSchema
35
- ? parseNonLiteral ( schema as SchemaSchema , rootSchema , keyName , keyNameFromDefinition , set , processed )
38
+ ? parseNonLiteral ( schema as SchemaSchema , rootSchema , keyName , keyNameFromDefinition , set , processed , usedNames )
36
39
: parseLiteral ( schema , keyName , keyNameFromDefinition , set )
37
40
}
38
41
@@ -56,7 +59,8 @@ function parseNonLiteral(
56
59
keyName : string | undefined ,
57
60
keyNameFromDefinition : string | undefined ,
58
61
set : ( ast : AST ) => AST ,
59
- processed : Processed
62
+ processed : Processed ,
63
+ usedNames : UsedNames
60
64
) {
61
65
62
66
log ( whiteBright . bgBlue ( 'parser' ) , schema , '<-' + typeOfSchema ( schema ) , processed . has ( schema ) ? '(FROM CACHE)' : '' )
@@ -66,72 +70,72 @@ function parseNonLiteral(
66
70
return set ( {
67
71
comment : schema . description ,
68
72
keyName,
69
- params : schema . allOf ! . map ( _ => parse ( _ , rootSchema , undefined , true , processed ) ) ,
70
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
73
+ params : schema . allOf ! . map ( _ => parse ( _ , rootSchema , undefined , true , processed , usedNames ) ) ,
74
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
71
75
type : 'INTERSECTION'
72
76
} )
73
77
case 'ANY' :
74
78
return set ( {
75
79
comment : schema . description ,
76
80
keyName,
77
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
81
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
78
82
type : 'ANY'
79
83
} )
80
84
case 'ANY_OF' :
81
85
return set ( {
82
86
comment : schema . description ,
83
87
keyName,
84
- params : schema . anyOf ! . map ( _ => parse ( _ , rootSchema , undefined , true , processed ) ) ,
85
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
88
+ params : schema . anyOf ! . map ( _ => parse ( _ , rootSchema , undefined , true , processed , usedNames ) ) ,
89
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
86
90
type : 'UNION'
87
91
} )
88
92
case 'BOOLEAN' :
89
93
return set ( {
90
94
comment : schema . description ,
91
95
keyName,
92
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
96
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
93
97
type : 'BOOLEAN'
94
98
} )
95
99
case 'NAMED_ENUM' :
96
100
return set ( {
97
101
comment : schema . description ,
98
102
keyName,
99
103
params : schema . enum ! . map ( ( _ , n ) => ( {
100
- ast : parse ( _ , rootSchema , undefined , false , processed ) ,
104
+ ast : parse ( _ , rootSchema , undefined , false , processed , usedNames ) ,
101
105
keyName : schema . tsEnumNames ! [ n ]
102
106
} ) ) ,
103
- standaloneName : schema . title || keyName ! ,
107
+ standaloneName : standaloneName ( schema , keyName , usedNames ) ! ,
104
108
type : 'ENUM'
105
109
} )
106
110
case 'NAMED_SCHEMA' :
107
- return set ( newInterface ( schema as SchemaSchema , rootSchema , processed , keyName ) )
111
+ return set ( newInterface ( schema as SchemaSchema , rootSchema , processed , usedNames , keyName ) )
108
112
case 'NULL' :
109
113
return set ( {
110
114
comment : schema . description ,
111
115
keyName,
112
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
116
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
113
117
type : 'NULL'
114
118
} )
115
119
case 'NUMBER' :
116
120
return set ( {
117
121
comment : schema . description ,
118
122
keyName,
119
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
123
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
120
124
type : 'NUMBER'
121
125
} )
122
126
case 'OBJECT' :
123
127
return set ( {
124
128
comment : schema . description ,
125
129
keyName,
126
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
130
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
127
131
type : 'OBJECT'
128
132
} )
129
133
case 'ONE_OF' :
130
134
return set ( {
131
135
comment : schema . description ,
132
136
keyName,
133
- params : schema . oneOf ! . map ( _ => parse ( _ , rootSchema , undefined , true , processed ) ) ,
134
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
137
+ params : schema . oneOf ! . map ( _ => parse ( _ , rootSchema , undefined , true , processed , usedNames ) ) ,
138
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
135
139
type : 'UNION'
136
140
} )
137
141
case 'REFERENCE' :
@@ -140,76 +144,92 @@ function parseNonLiteral(
140
144
return set ( {
141
145
comment : schema . description ,
142
146
keyName,
143
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
147
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
144
148
type : 'STRING'
145
149
} )
146
150
case 'TYPED_ARRAY' :
147
151
if ( Array . isArray ( schema . items ) ) {
148
152
return set ( {
149
153
comment : schema . description ,
150
154
keyName,
151
- params : schema . items . map ( _ => parse ( _ , rootSchema , undefined , true , processed ) ) ,
152
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
155
+ params : schema . items . map ( _ => parse ( _ , rootSchema , undefined , true , processed , usedNames ) ) ,
156
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
153
157
type : 'TUPLE'
154
158
} )
155
159
} else {
156
160
return set ( {
157
161
comment : schema . description ,
158
162
keyName,
159
- params : parse ( schema . items ! , rootSchema , undefined , true , processed ) ,
160
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
163
+ params : parse ( schema . items ! , rootSchema , undefined , true , processed , usedNames ) ,
164
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
161
165
type : 'ARRAY'
162
166
} )
163
167
}
164
168
case 'UNION' :
165
169
return set ( {
166
170
comment : schema . description ,
167
171
keyName,
168
- params : ( schema . type as JSONSchema4TypeName [ ] ) . map ( _ => parse ( { type : _ } , rootSchema , undefined , true , processed ) ) ,
169
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
172
+ params : ( schema . type as JSONSchema4TypeName [ ] ) . map ( _ => parse ( { type : _ } , rootSchema , undefined , true , processed , usedNames ) ) ,
173
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
170
174
type : 'UNION'
171
175
} )
172
176
case 'UNNAMED_ENUM' :
173
177
return set ( {
174
178
comment : schema . description ,
175
179
keyName,
176
- params : schema . enum ! . map ( _ => parse ( _ , rootSchema , undefined , false , processed ) ) ,
177
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
180
+ params : schema . enum ! . map ( _ => parse ( _ , rootSchema , undefined , false , processed , usedNames ) ) ,
181
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
178
182
type : 'UNION'
179
183
} )
180
184
case 'UNNAMED_SCHEMA' :
181
- return set ( newInterface ( schema as SchemaSchema , rootSchema , processed , keyName , keyNameFromDefinition ) )
185
+ return set ( newInterface ( schema as SchemaSchema , rootSchema , processed , usedNames , keyName , keyNameFromDefinition ) )
182
186
case 'UNTYPED_ARRAY' :
183
187
return set ( {
184
188
comment : schema . description ,
185
189
keyName,
186
190
params : T_ANY ,
187
- standaloneName : schema . title || schema . id || keyNameFromDefinition ,
191
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
188
192
type : 'ARRAY'
189
193
} )
190
194
}
191
195
}
192
196
197
+ /**
198
+ * Compute a schema name using a series of fallbacks
199
+ */
200
+ function standaloneName (
201
+ schema : JSONSchema ,
202
+ keyNameFromDefinition : string | undefined ,
203
+ usedNames : UsedNames
204
+ ) {
205
+ let name = schema . title || schema . id || keyNameFromDefinition
206
+ if ( name ) {
207
+ return generateName ( name , usedNames )
208
+ }
209
+ }
210
+
193
211
function newInterface (
194
212
schema : SchemaSchema ,
195
213
rootSchema : JSONSchema ,
196
214
processed : Processed ,
215
+ usedNames : UsedNames ,
197
216
keyName ?: string ,
198
217
keyNameFromDefinition ?: string
199
218
) : TInterface {
200
219
return {
201
220
comment : schema . description ,
202
221
keyName,
203
- params : parseSchema ( schema , rootSchema , processed ) ,
204
- standaloneName : computeSchemaName ( schema ) || keyNameFromDefinition ,
205
- superTypes : parseSuperTypes ( schema , processed ) ,
222
+ params : parseSchema ( schema , rootSchema , processed , usedNames ) ,
223
+ standaloneName : standaloneName ( schema , keyNameFromDefinition , usedNames ) ,
224
+ superTypes : parseSuperTypes ( schema , processed , usedNames ) ,
206
225
type : 'INTERFACE'
207
226
}
208
227
}
209
228
210
229
function parseSuperTypes (
211
230
schema : SchemaSchema ,
212
- processed : Processed
231
+ processed : Processed ,
232
+ usedNames : UsedNames
213
233
) : TNamedInterface [ ] {
214
234
// Type assertion needed because of dereferencing step
215
235
// TODO: Type it upstream
@@ -218,42 +238,37 @@ function parseSuperTypes(
218
238
return [ ]
219
239
}
220
240
if ( Array . isArray ( superTypes ) ) {
221
- return superTypes . map ( _ => newNamedInterface ( _ , _ , processed ) )
241
+ return superTypes . map ( _ => newNamedInterface ( _ , _ , processed , usedNames ) )
222
242
}
223
- return [ newNamedInterface ( superTypes , superTypes , processed ) ]
243
+ return [ newNamedInterface ( superTypes , superTypes , processed , usedNames ) ]
224
244
}
225
245
226
246
function newNamedInterface (
227
247
schema : SchemaSchema ,
228
248
rootSchema : JSONSchema ,
229
- processed : Processed
249
+ processed : Processed ,
250
+ usedNames : UsedNames
230
251
) : TNamedInterface {
231
- const namedInterface = newInterface ( schema , rootSchema , processed )
252
+ const namedInterface = newInterface ( schema , rootSchema , processed , usedNames )
232
253
if ( hasStandaloneName ( namedInterface ) ) {
233
254
return namedInterface
234
255
}
235
256
// TODO: Generate name if it doesn't have one
236
257
throw error ( 'Supertype must have standalone name!' , namedInterface )
237
258
}
238
259
239
- /**
240
- * Compute a schema name using a series of fallbacks
241
- */
242
- function computeSchemaName ( schema : SchemaSchema ) : string | undefined {
243
- return schema . title || schema . id
244
- }
245
-
246
260
/**
247
261
* Helper to parse schema properties into params on the parent schema's type
248
262
*/
249
263
function parseSchema (
250
264
schema : SchemaSchema ,
251
265
rootSchema : JSONSchema ,
252
- processed : Processed
266
+ processed : Processed ,
267
+ usedNames : UsedNames
253
268
) : TInterfaceParam [ ] {
254
269
255
270
const asts = map ( schema . properties , ( value , key : string ) => ( {
256
- ast : parse ( value , rootSchema , key , true , processed ) ,
271
+ ast : parse ( value , rootSchema , key , true , processed , usedNames ) ,
257
272
isRequired : includes ( schema . required || [ ] , key ) ,
258
273
keyName : key
259
274
} ) )
@@ -275,7 +290,7 @@ function parseSchema(
275
290
// defined via index signatures are already optional
276
291
default :
277
292
return asts . concat ( {
278
- ast : parse ( schema . additionalProperties , rootSchema , '[k: string]' , true , processed ) ,
293
+ ast : parse ( schema . additionalProperties , rootSchema , '[k: string]' , true , processed , usedNames ) ,
279
294
isRequired : true ,
280
295
keyName : '[k: string]'
281
296
} )
0 commit comments