@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.
- package/dist/index.cjs +1806 -3
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +590 -4
- package/dist/index.js +1776 -2
- package/dist/index.js.map +1 -0
- package/package.json +7 -27
- package/src/components/Enum.tsx +82 -0
- package/src/components/Type.tsx +29 -162
- package/src/constants.ts +39 -0
- package/src/factory.ts +134 -49
- package/src/generators/typeGenerator.tsx +165 -428
- package/src/generators/typeGeneratorLegacy.tsx +349 -0
- package/src/index.ts +9 -1
- package/src/plugin.ts +98 -176
- package/src/presets.ts +28 -0
- package/src/printers/functionPrinter.ts +196 -0
- package/src/printers/printerTs.ts +310 -0
- package/src/resolvers/resolverTs.ts +66 -0
- package/src/resolvers/resolverTsLegacy.ts +60 -0
- package/src/types.ts +258 -98
- package/src/utils.ts +131 -0
- package/dist/components-CRjwjdyE.js +0 -725
- package/dist/components-CRjwjdyE.js.map +0 -1
- package/dist/components-DI0aTIBg.cjs +0 -978
- package/dist/components-DI0aTIBg.cjs.map +0 -1
- package/dist/components.cjs +0 -3
- package/dist/components.d.ts +0 -38
- package/dist/components.js +0 -2
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -503
- package/dist/generators.js +0 -2
- package/dist/plugin-D5rCK1zO.cjs +0 -992
- package/dist/plugin-D5rCK1zO.cjs.map +0 -1
- package/dist/plugin-DmwgRHK8.js +0 -944
- package/dist/plugin-DmwgRHK8.js.map +0 -1
- package/dist/types-BpeKGgCn.d.ts +0 -170
- package/src/components/index.ts +0 -1
- package/src/components/v2/Type.tsx +0 -165
- package/src/generators/index.ts +0 -2
- package/src/generators/v2/typeGenerator.tsx +0 -196
- package/src/parser.ts +0 -396
- package/src/printer.ts +0 -244
package/src/components/Type.tsx
CHANGED
|
@@ -1,159 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
22
|
-
syntaxType: PluginTs['resolvedOptions']['syntaxType']
|
|
23
|
-
description?: string
|
|
24
|
-
keysToOmit?: string[]
|
|
21
|
+
resolver: PluginTs['resolver']
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
export function Type({
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
93
|
-
type = factory.createUnionDeclaration({
|
|
94
|
-
nodes: [type, factory.keywordTypeNodes.null],
|
|
95
|
-
}) as ts.TypeNode
|
|
96
|
-
}
|
|
32
|
+
const output = printer.print(node)
|
|
97
33
|
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
154
|
-
|
|
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(({
|
|
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={
|
|
188
|
-
{
|
|
54
|
+
<File.Source name={name} isTypeOnly isExportable isIndexable>
|
|
55
|
+
{output}
|
|
189
56
|
</File.Source>
|
|
190
57
|
)}
|
|
191
58
|
</>
|
package/src/constants.ts
ADDED
|
@@ -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
|
|
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
|
-
//
|
|
667
|
-
|
|
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
|
-
|
|
672
|
-
|
|
645
|
+
if (!normalized.includes('{')) {
|
|
646
|
+
return factory.createLiteralTypeNode(factory.createStringLiteral(normalized))
|
|
647
|
+
}
|
|
673
648
|
|
|
674
|
-
|
|
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)
|
|
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
|
+
}
|