@kubb/plugin-ts 5.0.0-alpha.3 → 5.0.0-alpha.30

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 (42) hide show
  1. package/dist/index.cjs +1806 -3
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.ts +590 -4
  4. package/dist/index.js +1776 -2
  5. package/dist/index.js.map +1 -0
  6. package/package.json +7 -27
  7. package/src/components/Enum.tsx +82 -0
  8. package/src/components/Type.tsx +29 -162
  9. package/src/constants.ts +39 -0
  10. package/src/factory.ts +134 -49
  11. package/src/generators/typeGenerator.tsx +165 -428
  12. package/src/generators/typeGeneratorLegacy.tsx +349 -0
  13. package/src/index.ts +9 -1
  14. package/src/plugin.ts +98 -176
  15. package/src/presets.ts +28 -0
  16. package/src/printers/functionPrinter.ts +196 -0
  17. package/src/printers/printerTs.ts +310 -0
  18. package/src/resolvers/resolverTs.ts +66 -0
  19. package/src/resolvers/resolverTsLegacy.ts +60 -0
  20. package/src/types.ts +258 -98
  21. package/src/utils.ts +131 -0
  22. package/dist/components-CRjwjdyE.js +0 -725
  23. package/dist/components-CRjwjdyE.js.map +0 -1
  24. package/dist/components-DI0aTIBg.cjs +0 -978
  25. package/dist/components-DI0aTIBg.cjs.map +0 -1
  26. package/dist/components.cjs +0 -3
  27. package/dist/components.d.ts +0 -38
  28. package/dist/components.js +0 -2
  29. package/dist/generators.cjs +0 -4
  30. package/dist/generators.d.ts +0 -503
  31. package/dist/generators.js +0 -2
  32. package/dist/plugin-D5rCK1zO.cjs +0 -992
  33. package/dist/plugin-D5rCK1zO.cjs.map +0 -1
  34. package/dist/plugin-DmwgRHK8.js +0 -944
  35. package/dist/plugin-DmwgRHK8.js.map +0 -1
  36. package/dist/types-BpeKGgCn.d.ts +0 -170
  37. package/src/components/index.ts +0 -1
  38. package/src/components/v2/Type.tsx +0 -165
  39. package/src/generators/index.ts +0 -2
  40. package/src/generators/v2/typeGenerator.tsx +0 -196
  41. package/src/parser.ts +0 -396
  42. package/src/printer.ts +0 -244
@@ -1,159 +1,44 @@
1
- import { camelCase, jsStringEscape, pascalCase, trimQuotes } from '@internals/utils'
2
- import { safePrint } from '@kubb/fabric-core/parsers/typescript'
3
- import type { SchemaObject } from '@kubb/oas'
4
- import { isKeyword, type Schema, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
1
+ import type { Printer } from '@kubb/ast'
2
+ import { collect, narrowSchema, schemaTypes } from '@kubb/ast'
3
+ import type { EnumSchemaNode, SchemaNode } from '@kubb/ast/types'
5
4
  import { File } from '@kubb/react-fabric'
6
5
  import type { FabricReactNode } from '@kubb/react-fabric/types'
7
- import type ts from 'typescript'
8
- import * as factory from '../factory.ts'
9
- import { parse, typeKeywordMapper } from '../parser.ts'
6
+ import type { PrinterTsFactory } from '../printers/printerTs.ts'
10
7
  import type { PluginTs } from '../types.ts'
8
+ import { Enum, getEnumNames } from './Enum.tsx'
11
9
 
12
10
  type Props = {
13
11
  name: string
14
- typedName: string
15
- schema: SchemaObject
16
- tree: Array<Schema>
17
- optionalType: PluginTs['resolvedOptions']['optionalType']
18
- arrayType: PluginTs['resolvedOptions']['arrayType']
12
+ node: SchemaNode
13
+ /**
14
+ * Pre-configured printer instance created by the generator.
15
+ * Created with `printerTs({ ..., nodes: options.printer?.nodes })`.
16
+ */
17
+ printer: Printer<PrinterTsFactory>
19
18
  enumType: PluginTs['resolvedOptions']['enumType']
19
+ enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
20
20
  enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
21
- mapper: PluginTs['resolvedOptions']['mapper']
22
- syntaxType: PluginTs['resolvedOptions']['syntaxType']
23
- description?: string
24
- keysToOmit?: string[]
21
+ resolver: PluginTs['resolver']
25
22
  }
26
23
 
27
- export function Type({
28
- name,
29
- typedName,
30
- tree,
31
- keysToOmit,
32
- schema,
33
- optionalType,
34
- arrayType,
35
- syntaxType,
36
- enumType,
37
- enumKeyCasing,
38
- mapper,
39
- description,
40
- }: Props): FabricReactNode {
41
- const typeNodes: ts.Node[] = []
42
-
43
- if (!tree.length) {
44
- return ''
45
- }
46
-
47
- const schemaFromTree = tree.find((item) => item.keyword === schemaKeywords.schema)
48
- const enumSchemas = SchemaGenerator.deepSearch(tree, schemaKeywords.enum)
49
-
50
- let type =
51
- (tree
52
- .map((current, _index, siblings) =>
53
- parse(
54
- { name, schema, parent: undefined, current, siblings },
55
- {
56
- optionalType,
57
- arrayType,
58
- enumType,
59
- mapper,
60
- },
61
- ),
62
- )
63
- .filter(Boolean)
64
- .at(0) as ts.TypeNode) || typeKeywordMapper.undefined()
65
-
66
- // Add a "Key" suffix to avoid collisions where necessary
67
- if (['asConst', 'asPascalConst'].includes(enumType) && enumSchemas.length > 0) {
68
- const isDirectEnum = schema.type === 'array' && schema.items !== undefined
69
- const isEnumOnly = 'enum' in schema && schema.enum
70
-
71
- if (isDirectEnum || isEnumOnly) {
72
- const enumSchema = enumSchemas[0]!
73
- const typeNameWithKey = `${enumSchema.args.typeName}Key`
74
-
75
- type = factory.createTypeReferenceNode(typeNameWithKey)
76
-
77
- if (schema.type === 'array') {
78
- if (arrayType === 'generic') {
79
- type = factory.createTypeReferenceNode(factory.createIdentifier('Array'), [type])
80
- } else {
81
- type = factory.createArrayTypeNode(type)
82
- }
83
- }
84
- }
85
- }
86
-
87
- if (schemaFromTree && isKeyword(schemaFromTree, schemaKeywords.schema)) {
88
- const isNullish = tree.some((item) => item.keyword === schemaKeywords.nullish)
89
- const isNullable = tree.some((item) => item.keyword === schemaKeywords.nullable)
90
- const isOptional = tree.some((item) => item.keyword === schemaKeywords.optional)
24
+ export function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, resolver }: Props): FabricReactNode {
25
+ const enumSchemaNodes = collect<EnumSchemaNode>(node, {
26
+ schema(n): EnumSchemaNode | undefined {
27
+ const enumNode = narrowSchema(n, schemaTypes.enum)
28
+ if (enumNode?.name) return enumNode
29
+ },
30
+ })
91
31
 
92
- if (isNullable) {
93
- type = factory.createUnionDeclaration({
94
- nodes: [type, factory.keywordTypeNodes.null],
95
- }) as ts.TypeNode
96
- }
32
+ const output = printer.print(node)
97
33
 
98
- if (isNullish && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
99
- type = factory.createUnionDeclaration({
100
- nodes: [type, factory.keywordTypeNodes.undefined],
101
- }) as ts.TypeNode
102
- }
103
-
104
- if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
105
- type = factory.createUnionDeclaration({
106
- nodes: [type, factory.keywordTypeNodes.undefined],
107
- }) as ts.TypeNode
108
- }
34
+ if (!output) {
35
+ return
109
36
  }
110
37
 
111
- const useTypeGeneration = syntaxType === 'type' || [factory.syntaxKind.union].includes(type.kind as typeof factory.syntaxKind.union) || !!keysToOmit?.length
112
-
113
- typeNodes.push(
114
- factory.createTypeDeclaration({
115
- name,
116
- isExportable: true,
117
- type: keysToOmit?.length
118
- ? factory.createOmitDeclaration({
119
- keys: keysToOmit,
120
- type,
121
- nonNullable: true,
122
- })
123
- : type,
124
- syntax: useTypeGeneration ? 'type' : 'interface',
125
- comments: [
126
- schema.title ? `${jsStringEscape(schema.title)}` : undefined,
127
- description ? `@description ${jsStringEscape(description)}` : undefined,
128
- schema.deprecated ? '@deprecated' : undefined,
129
- schema.minLength ? `@minLength ${schema.minLength}` : undefined,
130
- schema.maxLength ? `@maxLength ${schema.maxLength}` : undefined,
131
- schema.pattern ? `@pattern ${schema.pattern}` : undefined,
132
- schema.default ? `@default ${schema.default}` : undefined,
133
- schema.example ? `@example ${schema.example}` : undefined,
134
- ],
135
- }),
136
- )
137
-
138
- const enums = [...new Set(enumSchemas)].map((enumSchema) => {
139
- const name = enumType === 'asPascalConst' ? pascalCase(enumSchema.args.name) : camelCase(enumSchema.args.name)
140
- const typeName = ['asConst', 'asPascalConst'].includes(enumType) ? `${enumSchema.args.typeName}Key` : enumSchema.args.typeName
141
-
142
- const [nameNode, typeNode] = factory.createEnumDeclaration({
143
- name,
144
- typeName,
145
- enums: enumSchema.args.items
146
- .map((item) => (item.value === undefined ? undefined : [trimQuotes(item.name?.toString()), item.value]))
147
- .filter(Boolean) as unknown as Array<[string, string]>,
148
- type: enumType,
149
- enumKeyCasing,
150
- })
151
-
38
+ const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
152
39
  return {
153
- nameNode,
154
- typeNode,
155
- name,
156
- typeName,
40
+ node,
41
+ ...getEnumNames({ node, enumType, enumTypeSuffix, resolver }),
157
42
  }
158
43
  })
159
44
 
@@ -164,28 +49,10 @@ export function Type({
164
49
  return (
165
50
  <>
166
51
  {shouldExportEnums &&
167
- enums.map(({ name, nameNode, typeName, typeNode }) => (
168
- <>
169
- {nameNode && (
170
- <File.Source name={name} isExportable isIndexable isTypeOnly={false}>
171
- {safePrint(nameNode)}
172
- </File.Source>
173
- )}
174
- {
175
- <File.Source
176
- name={typeName}
177
- isIndexable
178
- isExportable={['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined].includes(enumType)}
179
- isTypeOnly={['asConst', 'asPascalConst', 'literal', undefined].includes(enumType)}
180
- >
181
- {safePrint(typeNode)}
182
- </File.Source>
183
- }
184
- </>
185
- ))}
52
+ enums.map(({ node }) => <Enum node={node} enumType={enumType} enumTypeSuffix={enumTypeSuffix} enumKeyCasing={enumKeyCasing} resolver={resolver} />)}
186
53
  {shouldExportType && (
187
- <File.Source name={typedName} isTypeOnly isExportable isIndexable>
188
- {safePrint(...typeNodes)}
54
+ <File.Source name={name} isTypeOnly isExportable isIndexable>
55
+ {output}
189
56
  </File.Source>
190
57
  )}
191
58
  </>
@@ -0,0 +1,39 @@
1
+ import type { PluginTs } from './types.ts'
2
+
3
+ type OptionalType = PluginTs['resolvedOptions']['optionalType']
4
+ type EnumType = PluginTs['resolvedOptions']['enumType']
5
+
6
+ /**
7
+ * `optionalType` values that cause a property's type to include `| undefined`.
8
+ */
9
+ export const OPTIONAL_ADDS_UNDEFINED = new Set<OptionalType>(['undefined', 'questionTokenAndUndefined'] as const)
10
+
11
+ /**
12
+ * `optionalType` values that render the property key with a `?` token.
13
+ */
14
+ export const OPTIONAL_ADDS_QUESTION_TOKEN = new Set<OptionalType>(['questionToken', 'questionTokenAndUndefined'] as const)
15
+
16
+ /**
17
+ * `enumType` values that append a `Key` suffix to the generated enum type alias.
18
+ */
19
+ export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst', 'asPascalConst'] as const)
20
+
21
+ /**
22
+ * `enumType` values that require a runtime value declaration (object, enum, or literal).
23
+ */
24
+ export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined] as const)
25
+
26
+ /**
27
+ * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
28
+ */
29
+ export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'asPascalConst', 'literal', undefined] as const)
30
+
31
+ /**
32
+ * Ordering priority for function parameters: lower = sorted earlier.
33
+ */
34
+ export const PARAM_RANK = {
35
+ required: 0,
36
+ optional: 1,
37
+ withDefault: 2,
38
+ rest: 3,
39
+ } as const
package/src/factory.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { camelCase, pascalCase, screamingSnakeCase, snakeCase } from '@internals/utils'
2
+ import { syncSchemaRef } from '@kubb/ast'
3
+ import type { ArraySchemaNode, SchemaNode } from '@kubb/ast/types'
2
4
  import { isNumber, sortBy } from 'remeda'
3
5
  import ts from 'typescript'
6
+ import { OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
4
7
 
5
8
  const { SyntaxKind, factory } = ts
6
9
 
@@ -19,16 +22,6 @@ export const syntaxKind = {
19
22
  stringLiteral: SyntaxKind.StringLiteral,
20
23
  } as const
21
24
 
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
31
- }
32
25
  function isValidIdentifier(str: string): boolean {
33
26
  if (!str.length || str.trim() !== str) {
34
27
  return false
@@ -76,28 +69,6 @@ export function createIntersectionDeclaration({ nodes, withParentheses }: { node
76
69
  return node
77
70
  }
78
71
 
79
- /**
80
- * Minimum nodes length of 2
81
- * @example `string & number`
82
- */
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
72
  export function createArrayDeclaration({ nodes, arrayType = 'array' }: { nodes: Array<ts.TypeNode>; arrayType?: 'array' | 'generic' }): ts.TypeNode | null {
102
73
  if (!nodes.length) {
103
74
  return factory.createTupleTypeNode([])
@@ -281,7 +252,7 @@ export function createTypeDeclaration({
281
252
  }) {
282
253
  if (syntax === 'interface' && 'members' in type) {
283
254
  const node = createInterfaceDeclaration({
284
- members: type.members as Array<ts.TypeElement>,
255
+ members: [...(type as ts.TypeLiteralNode).members],
285
256
  modifiers: isExportable ? [modifiers.export] : [],
286
257
  name,
287
258
  typeParameters: undefined,
@@ -662,43 +633,39 @@ export const keywordTypeNodes = {
662
633
  * Converts a path like '/pet/{petId}/uploadImage' to a template literal type
663
634
  * like `/pet/${string}/uploadImage`
664
635
  */
636
+ /**
637
+ * Converts an OAS-style path (e.g. `/pets/{petId}`) or an Express-style path
638
+ * (e.g. `/pets/:petId`) to a TypeScript template literal type
639
+ * like `` `/pets/${string}` ``.
640
+ */
665
641
  export function createUrlTemplateType(path: string): ts.TypeNode {
666
- // If no parameters, return literal string type
667
- if (!path.includes('{')) {
668
- return factory.createLiteralTypeNode(factory.createStringLiteral(path))
669
- }
642
+ // normalized Express `:param` OAS `{param}` so a single regex handles both.
643
+ const normalized = path.replace(/:([^/]+)/g, '{$1}')
670
644
 
671
- // Split path by parameter placeholders, e.g. '/pet/{petId}/upload' -> ['/pet/', 'petId', '/upload']
672
- const segments = path.split(/(\{[^}]+\})/)
645
+ if (!normalized.includes('{')) {
646
+ return factory.createLiteralTypeNode(factory.createStringLiteral(normalized))
647
+ }
673
648
 
674
- // Separate static parts from parameter placeholders
649
+ const segments = normalized.split(/(\{[^}]+\})/)
675
650
  const parts: string[] = []
676
651
  const parameterIndices: number[] = []
677
652
 
678
653
  segments.forEach((segment) => {
679
654
  if (segment.startsWith('{') && segment.endsWith('}')) {
680
- // This is a parameter placeholder
681
655
  parameterIndices.push(parts.length)
682
- parts.push(segment) // Will be replaced with ${string}
656
+ parts.push(segment)
683
657
  } else if (segment) {
684
- // This is a static part
685
658
  parts.push(segment)
686
659
  }
687
660
  })
688
661
 
689
- // Build template literal type
690
- // Template literal structure: head + templateSpans[]
691
- // For '/pet/{petId}/upload': head = '/pet/', spans = [{ type: string, literal: '/upload' }]
692
-
693
662
  const head = ts.factory.createTemplateHead(parts[0] || '')
694
663
  const templateSpans: ts.TemplateLiteralTypeSpan[] = []
695
664
 
696
665
  parameterIndices.forEach((paramIndex, i) => {
697
666
  const isLast = i === parameterIndices.length - 1
698
667
  const nextPart = parts[paramIndex + 1] || ''
699
-
700
668
  const literal = isLast ? ts.factory.createTemplateTail(nextPart) : ts.factory.createTemplateMiddle(nextPart)
701
-
702
669
  templateSpans.push(ts.factory.createTemplateLiteralTypeSpan(keywordTypeNodes.string, literal))
703
670
  })
704
671
 
@@ -728,3 +695,121 @@ export const createTypeOperatorNode = factory.createTypeOperatorNode
728
695
  export const createPrefixUnaryExpression = factory.createPrefixUnaryExpression
729
696
 
730
697
  export { SyntaxKind }
698
+
699
+ // ─── Printer helpers ──────────────────────────────────────────────────────────
700
+
701
+ /**
702
+ * Converts a primitive const value to a TypeScript literal type node.
703
+ * Handles negative numbers via a prefix unary expression.
704
+ */
705
+ export function constToTypeNode(value: string | number | boolean, format: 'string' | 'number' | 'boolean'): ts.TypeNode | undefined {
706
+ if (format === 'boolean') {
707
+ return createLiteralTypeNode(value === true ? createTrue() : createFalse())
708
+ }
709
+ if (format === 'number' && typeof value === 'number') {
710
+ if (value < 0) {
711
+ return createLiteralTypeNode(createPrefixUnaryExpression(SyntaxKind.MinusToken, createNumericLiteral(Math.abs(value))))
712
+ }
713
+ return createLiteralTypeNode(createNumericLiteral(value))
714
+ }
715
+ return createLiteralTypeNode(createStringLiteral(String(value)))
716
+ }
717
+
718
+ /**
719
+ * Returns a `Date` reference type node when `representation` is `'date'`, otherwise falls back to `string`.
720
+ */
721
+ export function dateOrStringNode(node: { representation?: string }): ts.TypeNode {
722
+ return node.representation === 'date' ? createTypeReferenceNode(createIdentifier('Date')) : keywordTypeNodes.string
723
+ }
724
+
725
+ /**
726
+ * Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
727
+ */
728
+ export function buildMemberNodes(members: Array<SchemaNode> | undefined, print: (node: SchemaNode) => ts.TypeNode | null | undefined): Array<ts.TypeNode> {
729
+ return (members ?? []).map(print).filter(Boolean)
730
+ }
731
+
732
+ /**
733
+ * Builds a TypeScript tuple type node from an array schema's `items`,
734
+ * applying min/max slice and optional/rest element rules.
735
+ */
736
+ export function buildTupleNode(node: ArraySchemaNode, print: (node: SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
737
+ let items = (node.items ?? []).map(print).filter(Boolean)
738
+
739
+ const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
740
+ const { min, max } = node
741
+
742
+ if (max !== undefined) {
743
+ items = items.slice(0, max)
744
+ if (items.length < max && restNode) {
745
+ items = [...items, ...Array(max - items.length).fill(restNode)]
746
+ }
747
+ }
748
+
749
+ if (min !== undefined) {
750
+ items = items.map((item, i) => (i >= min ? createOptionalTypeNode(item) : item))
751
+ }
752
+
753
+ if (max === undefined && restNode) {
754
+ items.push(createRestTypeNode(createArrayTypeNode(restNode)))
755
+ }
756
+
757
+ return createTupleTypeNode(items)
758
+ }
759
+
760
+ /**
761
+ * Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
762
+ */
763
+ export function buildPropertyType(
764
+ schema: SchemaNode,
765
+ baseType: ts.TypeNode,
766
+ optionalType: 'questionToken' | 'undefined' | 'questionTokenAndUndefined',
767
+ ): ts.TypeNode {
768
+ const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType)
769
+ const meta = syncSchemaRef(schema)
770
+
771
+ let type = baseType
772
+
773
+ if (meta.nullable) {
774
+ type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] })
775
+ }
776
+
777
+ if ((meta.nullish || meta.optional) && addsUndefined) {
778
+ type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] })
779
+ }
780
+
781
+ return type
782
+ }
783
+
784
+ /**
785
+ * Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
786
+ */
787
+ export function buildIndexSignatures(
788
+ node: { additionalProperties?: SchemaNode | boolean; patternProperties?: Record<string, SchemaNode> },
789
+ propertyCount: number,
790
+ print: (node: SchemaNode) => ts.TypeNode | null | undefined,
791
+ ): Array<ts.TypeElement> {
792
+ const elements: Array<ts.TypeElement> = []
793
+
794
+ if (node.additionalProperties && node.additionalProperties !== true) {
795
+ const additionalType = print(node.additionalProperties) ?? keywordTypeNodes.unknown
796
+
797
+ elements.push(createIndexSignature(propertyCount > 0 ? keywordTypeNodes.unknown : additionalType))
798
+ } else if (node.additionalProperties === true) {
799
+ elements.push(createIndexSignature(keywordTypeNodes.unknown))
800
+ }
801
+
802
+ if (node.patternProperties) {
803
+ const first = Object.values(node.patternProperties)[0]
804
+ if (first) {
805
+ let patternType = print(first) ?? keywordTypeNodes.unknown
806
+
807
+ if (first.nullable) {
808
+ patternType = createUnionDeclaration({ nodes: [patternType, keywordTypeNodes.null] })
809
+ }
810
+ elements.push(createIndexSignature(patternType))
811
+ }
812
+ }
813
+
814
+ return elements
815
+ }