@@ -197,7 +197,9 @@ function buildInterface (type: M.Interface): string {
197
197
const inherits = buildInherits ( type , openGenerics )
198
198
let code = `export interface ${ createName ( type . name ) } ${ buildGenerics ( type . generics , openGenerics ) } ${ inherits } {\n`
199
199
if ( type . properties . length === 0 && type . attachedBehaviors == null && inherits . length === 0 ) {
200
- code += ' [key: string]: never\n'
200
+ if ( process . env . KIBANA == null ) {
201
+ code += ' [key: string]: never\n'
202
+ }
201
203
} else {
202
204
for ( const property of type . properties ) {
203
205
code += ` ${ cleanPropertyName ( property . name ) } ${ required ( property ) } : ${ buildValue ( property . type , openGenerics ) } \n`
@@ -249,36 +251,81 @@ function buildBehaviorInterface (type: M.Interface): string {
249
251
}
250
252
}
251
253
254
+ // Handling additional properties has some caveats. There are 3 ways of defining an interface with additional dyamic properties:
255
+ //
256
+ // 1.
257
+ // interface Foo {
258
+ // prop: string
259
+ // [key: string]: string
260
+ // }
261
+ //
262
+ // 2.
263
+ // interface FooBase {
264
+ // prop: string
265
+ // }
266
+ // type Foo = FooBase & { [key: string]: string }
267
+ //
268
+ // 3.
269
+ // interface FooBase {
270
+ // prop: string
271
+ // }
272
+ // type Foo = FooBase | { [key: string]: number }
273
+ //
274
+ // 4.
275
+ // interface Foo {
276
+ // prop: string
277
+ // [key: string]: number | string
278
+ // }
279
+ //
280
+ // 1. and 2. are (almost) the same, the important thing is that the dynamic key can also describe every static key,
281
+ // in other words, the type of the static keys must be a subset of the type of the dynamic keys.
282
+ // If this is not possible, then we should use options 3.
283
+ // The best solution for now is 2, where we create an union of all the possible types for the dynamic keys
284
+ // (we must use an intersection because option 1 won't work with optional properties).
285
+ // The only drawback is that we might allow some type that won't work in the dynamic keys.
286
+ // Furthermore, we must take into account the types of the extended class properties (if present).
287
+ // The main difference with this approaches comes when you are actually using the types. 1 and 2 are good when
288
+ // you are reading an object of that type, while 3 is good when you are writing an object of that type.
252
289
function serializeAdditionalPropertiesType ( type : M . Interface ) : string {
253
290
const dictionaryOf = lookupBehavior ( type , 'AdditionalProperties' ) ?? lookupBehavior ( type , 'AdditionalProperty' )
254
291
if ( dictionaryOf == null ) throw new Error ( `Unknown implements ${ type . name . name } ` )
255
- let code = `export interface ${ createName ( type . name ) } Keys${ buildGenerics ( type . generics ) } ${ buildInherits ( type ) } {\n`
256
-
257
- function required ( property : M . Property ) : string {
258
- return property . required ? '' : '?'
259
- }
260
-
292
+ const extendedPropertyTypes = getAllExtendedPropertyTypes ( type . inherits )
261
293
const openGenerics = type . generics ?. map ( t => t . name ) ?? [ ]
294
+ let code = `export interface ${ createName ( type . name ) } Keys${ buildGenerics ( type . generics ) } ${ buildInherits ( type ) } {\n`
295
+ const types : Array < string | number | boolean > = [ ]
262
296
for ( const property of type . properties ) {
263
297
code += ` ${ cleanPropertyName ( property . name ) } ${ required ( property ) } : ${ buildValue ( property . type , openGenerics ) } \n`
298
+ types . push ( buildValue ( property . type , openGenerics ) )
264
299
}
265
300
code += '}\n'
266
- code += `export type ${ createName ( type . name ) } ${ buildGenerics ( type . generics , openGenerics ) } = ${ createName ( type . name ) } Keys${ buildGenerics ( type . generics , openGenerics , true ) } |\n`
267
- code += ` { ${ buildIndexer ( dictionaryOf , openGenerics ) } }\n`
301
+
302
+ // user_defined_value can always "contain" every other type
303
+ if ( Array . isArray ( dictionaryOf . generics ) && dictionaryOf . generics [ 1 ] . kind === 'user_defined_value' ) {
304
+ code += `export type ${ createName ( type . name ) } ${ buildGenerics ( type . generics ) } = ${ createName ( type . name ) } Keys${ buildGenerics ( type . generics , openGenerics , true ) } \n & { [property: string]: any }\n`
305
+ } else {
306
+ const dynamicTypes = Array . isArray ( dictionaryOf . generics )
307
+ ? [ ...new Set ( [ buildValue ( dictionaryOf . generics [ 1 ] , openGenerics ) ] . concat ( types ) . concat ( extendedPropertyTypes ) ) ]
308
+ : [ ...new Set ( types . concat ( extendedPropertyTypes ) ) ]
309
+ code += `export type ${ createName ( type . name ) } ${ buildGenerics ( type . generics ) } = ${ createName ( type . name ) } Keys${ buildGenerics ( type . generics , openGenerics , true ) } \n & { [property: string]: ${ dynamicTypes . join ( ' | ' ) } }\n`
310
+ }
268
311
return code
269
312
270
- function buildIndexer ( type : M . Inherits , openGenerics : string [ ] ) : string {
271
- if ( ! Array . isArray ( type . generics ) ) return ''
272
- assert ( type . generics . length === 2 )
273
- return `[property: string]: ${ buildGeneric ( type . generics [ 1 ] ) } `
274
-
275
- // generics can either be a value/instance_of or a named generics
276
- function buildGeneric ( type : M . ValueOf ) : string | number | boolean {
277
- const t = typeof type === 'string' ? type : buildValue ( type , openGenerics )
278
- // indexers do not allow type aliases so here we translate known
279
- // type aliases back to string
280
- return t === 'AggregateName' ? 'string' : t
313
+ function getAllExtendedPropertyTypes ( inherit ?: M . Inherits ) : Array < string | number | boolean > {
314
+ if ( inherit == null ) return [ ]
315
+ const extendedInterface = model . types . find ( type => inherit . type . name === type . name . name && inherit . type . namespace === type . name . namespace )
316
+ assert ( extendedInterface != null , `Can't find extended type for ${ inherit . type . namespace } .${ inherit . type . name } ` )
317
+ assert ( extendedInterface . kind === 'interface' , `We should be extending from another interface, instead got: ${ extendedInterface . kind } ` )
318
+ const openGenerics = extendedInterface . generics ?. map ( t => t . name ) ?? [ ]
319
+ const types : Array < string | number | boolean > = [ ]
320
+ for ( const property of extendedInterface . properties ) {
321
+ types . push ( buildValue ( property . type , openGenerics ) )
281
322
}
323
+ types . push ( ...getAllExtendedPropertyTypes ( extendedInterface . inherits ) )
324
+ return [ ...new Set ( types ) ]
325
+ }
326
+
327
+ function required ( property : M . Property ) : string {
328
+ return property . required ? '' : '?'
282
329
}
283
330
}
284
331
0 commit comments