@kubb/plugin-ts 5.0.0-alpha.9 → 5.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +26 -7
  3. package/dist/index.cjs +1526 -5
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +558 -27
  6. package/dist/index.js +1488 -2
  7. package/dist/index.js.map +1 -0
  8. package/extension.yaml +632 -0
  9. package/package.json +43 -65
  10. package/src/components/{v2/Enum.tsx → Enum.tsx} +33 -17
  11. package/src/components/Type.tsx +31 -161
  12. package/src/constants.ts +10 -0
  13. package/src/factory.ts +295 -39
  14. package/src/generators/typeGenerator.tsx +248 -420
  15. package/src/index.ts +9 -3
  16. package/src/plugin.ts +66 -205
  17. package/src/printers/functionPrinter.ts +197 -0
  18. package/src/printers/printerTs.ts +329 -0
  19. package/src/resolvers/resolverTs.ts +66 -0
  20. package/src/types.ts +233 -221
  21. package/src/utils.ts +129 -0
  22. package/dist/components-CRu8IKY3.js +0 -729
  23. package/dist/components-CRu8IKY3.js.map +0 -1
  24. package/dist/components-DeNDKlzf.cjs +0 -982
  25. package/dist/components-DeNDKlzf.cjs.map +0 -1
  26. package/dist/components.cjs +0 -3
  27. package/dist/components.d.ts +0 -36
  28. package/dist/components.js +0 -2
  29. package/dist/generators.cjs +0 -4
  30. package/dist/generators.d.ts +0 -509
  31. package/dist/generators.js +0 -2
  32. package/dist/plugin-BZkBwnEA.js +0 -1269
  33. package/dist/plugin-BZkBwnEA.js.map +0 -1
  34. package/dist/plugin-Bunz1oGa.cjs +0 -1322
  35. package/dist/plugin-Bunz1oGa.cjs.map +0 -1
  36. package/dist/types-mSXmB8WU.d.ts +0 -298
  37. package/src/components/index.ts +0 -1
  38. package/src/components/v2/Type.tsx +0 -59
  39. package/src/generators/index.ts +0 -2
  40. package/src/generators/v2/typeGenerator.tsx +0 -167
  41. package/src/generators/v2/utils.ts +0 -140
  42. package/src/parser.ts +0 -389
  43. package/src/printer.ts +0 -368
  44. package/src/resolverTs.ts +0 -77
package/src/factory.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import { camelCase, pascalCase, screamingSnakeCase, snakeCase } from '@internals/utils'
2
+ import { ast } from '@kubb/core'
2
3
  import { isNumber, sortBy } from 'remeda'
3
4
  import ts from 'typescript'
5
+ import { OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
4
6
 
5
7
  const { SyntaxKind, factory } = ts
6
8
 
7
9
  // https://ts-ast-viewer.com/
8
10
 
11
+ /**
12
+ * TypeScript AST modifiers for common keywords (async, export, const, static).
13
+ */
9
14
  export const modifiers = {
10
15
  async: factory.createModifier(ts.SyntaxKind.AsyncKeyword),
11
16
  export: factory.createModifier(ts.SyntaxKind.ExportKeyword),
@@ -13,22 +18,19 @@ export const modifiers = {
13
18
  static: factory.createModifier(ts.SyntaxKind.StaticKeyword),
14
19
  } as const
15
20
 
21
+ /**
22
+ * TypeScript syntax kind constants for union, literal, and string types.
23
+ */
16
24
  export const syntaxKind = {
17
25
  union: SyntaxKind.UnionType as 192,
18
26
  literalType: SyntaxKind.LiteralType,
19
27
  stringLiteral: SyntaxKind.StringLiteral,
20
28
  } as const
21
29
 
22
- export function getUnknownType(unknownType: 'any' | 'unknown' | 'void' | undefined) {
23
- if (unknownType === 'any') {
24
- return keywordTypeNodes.any
25
- }
26
- if (unknownType === 'void') {
27
- return keywordTypeNodes.void
28
- }
29
-
30
- return keywordTypeNodes.unknown
30
+ function isNonNullable<T>(value: T | null | undefined): value is T {
31
+ return value !== null && value !== undefined
31
32
  }
33
+
32
34
  function isValidIdentifier(str: string): boolean {
33
35
  if (!str.length || str.trim() !== str) {
34
36
  return false
@@ -48,6 +50,10 @@ function propertyName(name: string | ts.PropertyName): ts.PropertyName {
48
50
 
49
51
  const questionToken = factory.createToken(ts.SyntaxKind.QuestionToken)
50
52
 
53
+ /**
54
+ * Creates a question token for optional type annotations.
55
+ * Pass `true` to use the cached token, or provide a pre-created token.
56
+ */
51
57
  export function createQuestionToken(token?: boolean | ts.QuestionToken) {
52
58
  if (!token) {
53
59
  return undefined
@@ -58,6 +64,10 @@ export function createQuestionToken(token?: boolean | ts.QuestionToken) {
58
64
  return token
59
65
  }
60
66
 
67
+ /**
68
+ * Creates a TypeScript intersection type node from multiple type nodes.
69
+ * Returns the single node if only one is provided, or wraps in parentheses if requested.
70
+ */
61
71
  export function createIntersectionDeclaration({ nodes, withParentheses }: { nodes: Array<ts.TypeNode>; withParentheses?: boolean }): ts.TypeNode | null {
62
72
  if (!nodes.length) {
63
73
  return null
@@ -77,27 +87,15 @@ export function createIntersectionDeclaration({ nodes, withParentheses }: { node
77
87
  }
78
88
 
79
89
  /**
80
- * Minimum nodes length of 2
81
- * @example `string & number`
90
+ * Creates a TypeScript array type node.
91
+ * Use `arrayType: 'array'` for bracket syntax (`T[]`), or `'generic'` for `Array<T>`.
92
+ *
93
+ * @example Array bracket syntax
94
+ * `createArrayDeclaration({ nodes: [stringType], arrayType: 'array' }) // → string[]`
95
+ *
96
+ * @example Generic Array syntax
97
+ * `createArrayDeclaration({ nodes: [stringType], arrayType: 'generic' }) // → Array<string>`
82
98
  */
83
- export function createTupleDeclaration({ nodes, withParentheses }: { nodes: Array<ts.TypeNode>; withParentheses?: boolean }): ts.TypeNode | null {
84
- if (!nodes.length) {
85
- return null
86
- }
87
-
88
- if (nodes.length === 1) {
89
- return nodes[0] || null
90
- }
91
-
92
- const node = factory.createTupleTypeNode(nodes)
93
-
94
- if (withParentheses) {
95
- return factory.createParenthesizedType(node)
96
- }
97
-
98
- return node
99
- }
100
-
101
99
  export function createArrayDeclaration({ nodes, arrayType = 'array' }: { nodes: Array<ts.TypeNode>; arrayType?: 'array' | 'generic' }): ts.TypeNode | null {
102
100
  if (!nodes.length) {
103
101
  return factory.createTupleTypeNode([])
@@ -125,7 +123,8 @@ export function createArrayDeclaration({ nodes, arrayType = 'array' }: { nodes:
125
123
 
126
124
  /**
127
125
  * Minimum nodes length of 2
128
- * @example `string | number`
126
+ * @example Union type example
127
+ * `string | number`
129
128
  */
130
129
  export function createUnionDeclaration({ nodes, withParentheses }: { nodes: Array<ts.TypeNode>; withParentheses?: boolean }): ts.TypeNode {
131
130
  if (!nodes.length) {
@@ -145,6 +144,10 @@ export function createUnionDeclaration({ nodes, withParentheses }: { nodes: Arra
145
144
  return node
146
145
  }
147
146
 
147
+ /**
148
+ * Creates a TypeScript property signature for object/interface members.
149
+ * Supports optional markers, readonly modifiers, and type annotations.
150
+ */
148
151
  export function createPropertySignature({
149
152
  readOnly,
150
153
  modifiers = [],
@@ -159,13 +162,18 @@ export function createPropertySignature({
159
162
  type?: ts.TypeNode
160
163
  }) {
161
164
  return factory.createPropertySignature(
162
- [...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : undefined].filter(Boolean),
165
+ [...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : undefined].filter(
166
+ (modifier): modifier is ts.Modifier => modifier !== undefined,
167
+ ),
163
168
  propertyName(name),
164
169
  createQuestionToken(questionToken),
165
170
  type,
166
171
  )
167
172
  }
168
173
 
174
+ /**
175
+ * Creates a function parameter declaration with optional markers, rest parameters, and type annotations.
176
+ */
169
177
  export function createParameterSignature(
170
178
  name: string | ts.BindingName,
171
179
  {
@@ -186,6 +194,10 @@ export function createParameterSignature(
186
194
  return factory.createParameterDeclaration(modifiers, dotDotDotToken, name, createQuestionToken(questionToken), type, initializer)
187
195
  }
188
196
 
197
+ /**
198
+ * Creates a JSDoc comment node from an array of comment strings.
199
+ * Returns null if no comments are provided.
200
+ */
189
201
  export function createJSDoc({ comments }: { comments: string[] }) {
190
202
  if (!comments.length) {
191
203
  return null
@@ -204,7 +216,10 @@ export function createJSDoc({ comments }: { comments: string[] }) {
204
216
  }
205
217
 
206
218
  /**
207
- * @link https://github.com/microsoft/TypeScript/issues/44151
219
+ * Attaches JSDoc comments to an AST node as synthetic leading comments.
220
+ * Filters out undefined comments before attaching.
221
+ *
222
+ * @see https://github.com/microsoft/TypeScript/issues/44151
208
223
  */
209
224
  export function appendJSDocToNode<TNode extends ts.Node>({ node, comments }: { node: TNode; comments: Array<string | undefined> }) {
210
225
  const filteredComments = comments.filter(Boolean)
@@ -222,6 +237,10 @@ export function appendJSDocToNode<TNode extends ts.Node>({ node, comments }: { n
222
237
  return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `${text || '*'}\n`, true)
223
238
  }
224
239
 
240
+ /**
241
+ * Creates a TypeScript index signature for dynamic property access.
242
+ * Defines the key type (default: `string`) and value type on an object.
243
+ */
225
244
  export function createIndexSignature(
226
245
  type: ts.TypeNode,
227
246
  {
@@ -238,6 +257,9 @@ export function createIndexSignature(
238
257
  return factory.createIndexSignature(modifiers, [createParameterSignature(indexName, { type: indexType })], type)
239
258
  }
240
259
 
260
+ /**
261
+ * Creates a TypeScript type alias declaration with optional modifiers and type parameters.
262
+ */
241
263
  export function createTypeAliasDeclaration({
242
264
  modifiers,
243
265
  name,
@@ -252,6 +274,9 @@ export function createTypeAliasDeclaration({
252
274
  return factory.createTypeAliasDeclaration(modifiers, name, typeParameters, type)
253
275
  }
254
276
 
277
+ /**
278
+ * Creates a TypeScript interface declaration with optional modifiers, type parameters, and members.
279
+ */
255
280
  export function createInterfaceDeclaration({
256
281
  modifiers,
257
282
  name,
@@ -266,6 +291,10 @@ export function createInterfaceDeclaration({
266
291
  return factory.createInterfaceDeclaration(modifiers, name, typeParameters, undefined, members)
267
292
  }
268
293
 
294
+ /**
295
+ * Creates a TypeScript type declaration as either a type alias or interface.
296
+ * Intelligently selects the syntax based on the type structure and attaches JSDoc comments.
297
+ */
269
298
  export function createTypeDeclaration({
270
299
  syntax,
271
300
  isExportable,
@@ -281,7 +310,7 @@ export function createTypeDeclaration({
281
310
  }) {
282
311
  if (syntax === 'interface' && 'members' in type) {
283
312
  const node = createInterfaceDeclaration({
284
- members: type.members as Array<ts.TypeElement>,
313
+ members: [...(type as ts.TypeLiteralNode).members],
285
314
  modifiers: isExportable ? [modifiers.export] : [],
286
315
  name,
287
316
  typeParameters: undefined,
@@ -306,6 +335,9 @@ export function createTypeDeclaration({
306
335
  })
307
336
  }
308
337
 
338
+ /**
339
+ * Creates a TypeScript namespace declaration (exported module).
340
+ */
309
341
  export function createNamespaceDeclaration({ statements, name }: { name: string; statements: ts.Statement[] }) {
310
342
  return factory.createModuleDeclaration(
311
343
  [factory.createToken(ts.SyntaxKind.ExportKeyword)],
@@ -316,8 +348,17 @@ export function createNamespaceDeclaration({ statements, name }: { name: string;
316
348
  }
317
349
 
318
350
  /**
319
- * In { propertyName: string; name?: string } is `name` being used to make the type more unique when multiple same names are used.
320
- * @example `import { Pet as Cat } from './Pet'`
351
+ * Creates an import declaration with support for default imports, named imports, namespace imports, and type-only imports.
352
+ * Optionally rename imported members with `propertyName` and `name` pairs.
353
+ *
354
+ * @example Default import
355
+ * `import Pet from './Pet'`
356
+ *
357
+ * @example Named imports with rename
358
+ * `import { Pet as Cat } from './Pet'`
359
+ *
360
+ * @example Namespace import
361
+ * `import * as Pet from './Pet'`
321
362
  */
322
363
  export function createImportDeclaration({
323
364
  name,
@@ -375,6 +416,10 @@ export function createImportDeclaration({
375
416
  )
376
417
  }
377
418
 
419
+ /**
420
+ * Creates an export declaration with support for named exports, namespace exports, and type-only exports.
421
+ * Sorts export names alphabetically for consistent output across platforms.
422
+ */
378
423
  export function createExportDeclaration({
379
424
  path,
380
425
  asAlias,
@@ -440,6 +485,20 @@ function applyEnumKeyCasing(key: string, casing: 'screamingSnakeCase' | 'snakeCa
440
485
  return key
441
486
  }
442
487
 
488
+ /**
489
+ * Creates a TypeScript enum declaration or equivalent construct in various formats.
490
+ * Returns a tuple of [name node, type node] - name node may be undefined for certain types.
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * const [name, type] = createEnumDeclaration({
495
+ * type: 'enum',
496
+ * name: 'petType',
497
+ * typeName: 'PetType',
498
+ * enums: [['cat', 'cat'], ['dog', 'dog']],
499
+ * })
500
+ * ```
501
+ */
443
502
  export function createEnumDeclaration({
444
503
  type = 'enum',
445
504
  name,
@@ -500,7 +559,7 @@ export function createEnumDeclaration({
500
559
 
501
560
  return undefined
502
561
  })
503
- .filter(Boolean),
562
+ .filter((node): node is ts.LiteralTypeNode => node !== undefined),
504
563
  ),
505
564
  ),
506
565
  ]
@@ -510,7 +569,9 @@ export function createEnumDeclaration({
510
569
  return [
511
570
  undefined,
512
571
  factory.createEnumDeclaration(
513
- [factory.createToken(ts.SyntaxKind.ExportKeyword), type === 'constEnum' ? factory.createToken(ts.SyntaxKind.ConstKeyword) : undefined].filter(Boolean),
572
+ [factory.createToken(ts.SyntaxKind.ExportKeyword), type === 'constEnum' ? factory.createToken(ts.SyntaxKind.ConstKeyword) : undefined].filter(
573
+ (modifier): modifier is ts.ModifierToken<ts.SyntaxKind.ExportKeyword> | ts.ModifierToken<ts.SyntaxKind.ConstKeyword> => modifier !== undefined,
574
+ ),
514
575
  factory.createIdentifier(typeName),
515
576
  enums
516
577
  .map(([key, value]) => {
@@ -541,7 +602,7 @@ export function createEnumDeclaration({
541
602
 
542
603
  return undefined
543
604
  })
544
- .filter(Boolean),
605
+ .filter((member): member is ts.EnumMember => member !== undefined),
545
606
  ),
546
607
  ]
547
608
  }
@@ -604,7 +665,7 @@ export function createEnumDeclaration({
604
665
 
605
666
  return undefined
606
667
  })
607
- .filter(Boolean),
668
+ .filter((property): property is ts.PropertyAssignment => property !== undefined),
608
669
  true,
609
670
  ),
610
671
  factory.createTypeReferenceNode(factory.createIdentifier('const'), undefined),
@@ -626,6 +687,10 @@ export function createEnumDeclaration({
626
687
  ]
627
688
  }
628
689
 
690
+ /**
691
+ * Creates a TypeScript `Omit<T, Keys>` type reference node.
692
+ * Optionally wraps the type in `NonNullable<T>` if `nonNullable` is true.
693
+ */
629
694
  export function createOmitDeclaration({ keys, type, nonNullable }: { keys: Array<string> | string; type: ts.TypeNode; nonNullable?: boolean }) {
630
695
  const node = nonNullable ? factory.createTypeReferenceNode(factory.createIdentifier('NonNullable'), [type]) : type
631
696
 
@@ -643,6 +708,10 @@ export function createOmitDeclaration({ keys, type, nonNullable }: { keys: Array
643
708
  return factory.createTypeReferenceNode(factory.createIdentifier('Omit'), [node, factory.createLiteralTypeNode(factory.createStringLiteral(keys))])
644
709
  }
645
710
 
711
+ /**
712
+ * Pre-built TypeScript keyword type nodes for common primitive types.
713
+ * Use these to avoid repeatedly creating the same type nodes.
714
+ */
646
715
  export const keywordTypeNodes = {
647
716
  any: factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
648
717
  unknown: factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
@@ -701,26 +770,213 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
701
770
  return ts.factory.createTemplateLiteralType(head, templateSpans)
702
771
  }
703
772
 
773
+ /**
774
+ * Creates a TypeScript type literal node (anonymous object type).
775
+ */
704
776
  export const createTypeLiteralNode = factory.createTypeLiteralNode
705
777
 
778
+ /**
779
+ * Creates a TypeScript type reference node (e.g., `Array<string>`, `Record<K, V>`).
780
+ */
706
781
  export const createTypeReferenceNode = factory.createTypeReferenceNode
782
+
783
+ /**
784
+ * Creates a numeric literal type node.
785
+ */
707
786
  export const createNumericLiteral = factory.createNumericLiteral
787
+
788
+ /**
789
+ * Creates a string literal type node.
790
+ */
708
791
  export const createStringLiteral = factory.createStringLiteral
709
792
 
793
+ /**
794
+ * Creates an array type node (e.g., `T[]`).
795
+ */
710
796
  export const createArrayTypeNode = factory.createArrayTypeNode
797
+
798
+ /**
799
+ * Creates a parenthesized type node to control operator precedence.
800
+ */
711
801
  export const createParenthesizedType = factory.createParenthesizedType
712
802
 
803
+ /**
804
+ * Creates a literal type node (e.g., `'hello'`, `42`, `true`).
805
+ */
713
806
  export const createLiteralTypeNode = factory.createLiteralTypeNode
807
+
808
+ /**
809
+ * Creates a null literal type node.
810
+ */
714
811
  export const createNull = factory.createNull
812
+
813
+ /**
814
+ * Creates an identifier node.
815
+ */
715
816
  export const createIdentifier = factory.createIdentifier
716
817
 
818
+ /**
819
+ * Creates an optional type node (e.g., `T | undefined`).
820
+ */
717
821
  export const createOptionalTypeNode = factory.createOptionalTypeNode
822
+
823
+ /**
824
+ * Creates a tuple type node (e.g., `[string, number]`).
825
+ */
718
826
  export const createTupleTypeNode = factory.createTupleTypeNode
827
+
828
+ /**
829
+ * Creates a rest type node for variadic tuple elements (e.g., `...T[]`).
830
+ */
719
831
  export const createRestTypeNode = factory.createRestTypeNode
832
+
833
+ /**
834
+ * Creates a boolean true literal type node.
835
+ */
720
836
  export const createTrue = factory.createTrue
837
+
838
+ /**
839
+ * Creates a boolean false literal type node.
840
+ */
721
841
  export const createFalse = factory.createFalse
842
+
843
+ /**
844
+ * Creates an indexed access type node (e.g., `T[K]`).
845
+ */
722
846
  export const createIndexedAccessTypeNode = factory.createIndexedAccessTypeNode
847
+
848
+ /**
849
+ * Creates a type operator node (e.g., `keyof T`, `readonly T[]`).
850
+ */
723
851
  export const createTypeOperatorNode = factory.createTypeOperatorNode
852
+
853
+ /**
854
+ * Creates a prefix unary expression (e.g., negative numbers, logical not).
855
+ */
724
856
  export const createPrefixUnaryExpression = factory.createPrefixUnaryExpression
725
857
 
858
+ /**
859
+ * Exports TypeScript SyntaxKind enum for AST node type checking.
860
+ */
726
861
  export { SyntaxKind }
862
+
863
+ // ─── Printer helpers ──────────────────────────────────────────────────────────
864
+
865
+ /**
866
+ * Converts a primitive const value to a TypeScript literal type node.
867
+ * Handles negative numbers via a prefix unary expression.
868
+ */
869
+ export function constToTypeNode(value: string | number | boolean, format: 'string' | 'number' | 'boolean'): ts.TypeNode | undefined {
870
+ if (format === 'boolean') {
871
+ return createLiteralTypeNode(value === true ? createTrue() : createFalse())
872
+ }
873
+ if (format === 'number' && typeof value === 'number') {
874
+ if (value < 0) {
875
+ return createLiteralTypeNode(createPrefixUnaryExpression(SyntaxKind.MinusToken, createNumericLiteral(Math.abs(value))))
876
+ }
877
+ return createLiteralTypeNode(createNumericLiteral(value))
878
+ }
879
+ return createLiteralTypeNode(createStringLiteral(String(value)))
880
+ }
881
+
882
+ /**
883
+ * Returns a `Date` reference type node when `representation` is `'date'`, otherwise falls back to `string`.
884
+ */
885
+ export function dateOrStringNode(node: { representation?: string }): ts.TypeNode {
886
+ return node.representation === 'date' ? createTypeReferenceNode(createIdentifier('Date')) : keywordTypeNodes.string
887
+ }
888
+
889
+ /**
890
+ * Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
891
+ */
892
+ export function buildMemberNodes(
893
+ members: Array<ast.SchemaNode> | undefined,
894
+ print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined,
895
+ ): Array<ts.TypeNode> {
896
+ return (members ?? []).map(print).filter(isNonNullable)
897
+ }
898
+
899
+ /**
900
+ * Builds a TypeScript tuple type node from an array schema's `items`,
901
+ * applying min/max slice and optional/rest element rules.
902
+ */
903
+ export function buildTupleNode(node: ast.ArraySchemaNode, print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
904
+ let items = (node.items ?? []).map(print).filter(isNonNullable)
905
+
906
+ const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
907
+ const { min, max } = node
908
+
909
+ if (max !== undefined) {
910
+ items = items.slice(0, max)
911
+ if (items.length < max && restNode) {
912
+ items = [...items, ...Array(max - items.length).fill(restNode)]
913
+ }
914
+ }
915
+
916
+ if (min !== undefined) {
917
+ items = items.map((item, i) => (i >= min ? createOptionalTypeNode(item) : item))
918
+ }
919
+
920
+ if (max === undefined && restNode) {
921
+ items.push(createRestTypeNode(createArrayTypeNode(restNode)))
922
+ }
923
+
924
+ return createTupleTypeNode(items)
925
+ }
926
+
927
+ /**
928
+ * Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
929
+ */
930
+ export function buildPropertyType(
931
+ schema: ast.SchemaNode,
932
+ baseType: ts.TypeNode,
933
+ optionalType: 'questionToken' | 'undefined' | 'questionTokenAndUndefined',
934
+ ): ts.TypeNode {
935
+ const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType)
936
+ const meta = ast.syncSchemaRef(schema)
937
+
938
+ let type = baseType
939
+
940
+ if (meta.nullable) {
941
+ type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] })
942
+ }
943
+
944
+ if ((meta.nullish || meta.optional) && addsUndefined) {
945
+ type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] })
946
+ }
947
+
948
+ return type
949
+ }
950
+
951
+ /**
952
+ * Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
953
+ */
954
+ export function buildIndexSignatures(
955
+ node: { additionalProperties?: ast.SchemaNode | boolean; patternProperties?: Record<string, ast.SchemaNode> },
956
+ propertyCount: number,
957
+ print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined,
958
+ ): Array<ts.TypeElement> {
959
+ const elements: Array<ts.TypeElement> = []
960
+
961
+ if (node.additionalProperties && node.additionalProperties !== true) {
962
+ const additionalType = print(node.additionalProperties) ?? keywordTypeNodes.unknown
963
+
964
+ elements.push(createIndexSignature(propertyCount > 0 ? keywordTypeNodes.unknown : additionalType))
965
+ } else if (node.additionalProperties === true) {
966
+ elements.push(createIndexSignature(keywordTypeNodes.unknown))
967
+ }
968
+
969
+ if (node.patternProperties) {
970
+ const first = Object.values(node.patternProperties)[0]
971
+ if (first) {
972
+ let patternType = print(first) ?? keywordTypeNodes.unknown
973
+
974
+ if (first.nullable) {
975
+ patternType = createUnionDeclaration({ nodes: [patternType, keywordTypeNodes.null] })
976
+ }
977
+ elements.push(createIndexSignature(patternType))
978
+ }
979
+ }
980
+
981
+ return elements
982
+ }