@@ -529,6 +529,74 @@ function transformArraySchemaObject(schemaObject: ArraySchemaObject, options: Tr
529
529
return toOptionsReadonly ( tupleType , options ) ;
530
530
}
531
531
532
+ type ArraySchemaObject = SchemaObject & ArraySubtype ;
533
+ function isArraySchemaObject ( schemaObject : SchemaObject | ArraySchemaObject ) : schemaObject is ArraySchemaObject {
534
+ return schemaObject . type === "array" ;
535
+ }
536
+
537
+ function padTupleMembers ( length : number , itemType : ts . TypeNode , prefixTypes : readonly ts . TypeNode [ ] ) {
538
+ return Array . from ( { length } ) . map ( ( _ , index ) => {
539
+ return prefixTypes [ index ] ?? itemType ;
540
+ } ) ;
541
+ }
542
+
543
+ function toOptionsReadonly < TMembers extends ts . ArrayTypeNode | ts . TupleTypeNode > (
544
+ members : TMembers ,
545
+ options : TransformNodeOptions ,
546
+ ) : TMembers | ts . TypeOperatorNode {
547
+ return options . ctx . immutable ? ts . factory . createTypeOperatorNode ( ts . SyntaxKind . ReadonlyKeyword , members ) : members ;
548
+ }
549
+
550
+ /* Transform Array schema object */
551
+ function transformArraySchemaObject ( schemaObject : ArraySchemaObject , options : TransformNodeOptions ) : ts . TypeNode {
552
+ const prefixTypes = ( schemaObject . prefixItems ?? [ ] ) . map ( ( item ) => transformSchemaObject ( item , options ) ) ;
553
+
554
+ if ( Array . isArray ( schemaObject . items ) ) {
555
+ throw new Error ( `${ options . path } : invalid property items. Expected Schema Object, got Array` ) ;
556
+ }
557
+
558
+ const itemType = schemaObject . items ? transformSchemaObject ( schemaObject . items , options ) : UNKNOWN ;
559
+
560
+ // The minimum number of tuple members to return
561
+ const min : number =
562
+ options . ctx . arrayLength && typeof schemaObject . minItems === "number" && schemaObject . minItems >= 0
563
+ ? schemaObject . minItems
564
+ : 0 ;
565
+ const max : number | undefined =
566
+ options . ctx . arrayLength &&
567
+ typeof schemaObject . maxItems === "number" &&
568
+ schemaObject . maxItems >= 0 &&
569
+ min <= schemaObject . maxItems
570
+ ? schemaObject . maxItems
571
+ : undefined ;
572
+
573
+ // "30" is an arbitrary number but roughly around when TS starts to struggle with tuple inference in practice
574
+ const MAX_CODE_SIZE = 30 ;
575
+ const estimateCodeSize = max === undefined ? min : ( max * ( max + 1 ) - min * ( min - 1 ) ) / 2 ;
576
+ const shouldGeneratePermutations = ( min !== 0 || max !== undefined ) && estimateCodeSize < MAX_CODE_SIZE ;
577
+
578
+ // if maxItems is set, then return a union of all permutations of possible tuple types
579
+ if ( shouldGeneratePermutations && max !== undefined ) {
580
+ return tsUnion (
581
+ Array . from ( { length : max - min + 1 } ) . map ( ( _ , index ) =>
582
+ toOptionsReadonly ( ts . factory . createTupleTypeNode ( padTupleMembers ( index + min , itemType , prefixTypes ) ) , options ) ,
583
+ ) ,
584
+ ) ;
585
+ }
586
+
587
+ // if maxItems not set, then return a simple tuple type the length of `min`
588
+ const spreadType = ts . factory . createArrayTypeNode ( itemType ) ;
589
+ const tupleType =
590
+ shouldGeneratePermutations || prefixTypes . length
591
+ ? ts . factory . createTupleTypeNode ( [
592
+ ...padTupleMembers ( Math . max ( min , prefixTypes . length ) , itemType , prefixTypes ) ,
593
+ ts . factory . createRestTypeNode ( toOptionsReadonly ( spreadType , options ) ) ,
594
+ ] )
595
+ : spreadType ;
596
+
597
+ return toOptionsReadonly ( tupleType , options ) ;
598
+ }
599
+
532
600
/**
533
601
* Handle SchemaObject minus composition (anyOf/allOf/oneOf)
534
602
*/
0 commit comments