@kubb/plugin-ts 5.0.0-alpha.24 → 5.0.0-alpha.25
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 +468 -473
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.js +472 -477
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/Enum.tsx +1 -1
- package/src/constants.ts +10 -0
- package/src/factory.ts +122 -1
- package/src/generators/typeGenerator.tsx +107 -108
- package/src/generators/typeGeneratorLegacy.tsx +85 -80
- package/src/index.ts +0 -5
- package/src/plugin.ts +19 -49
- package/src/printers/functionPrinter.ts +6 -5
- package/src/printers/printerTs.ts +49 -181
- package/src/resolvers/resolverTs.ts +2 -2
- package/src/types.ts +6 -4
- package/src/utils.ts +41 -13
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isStringType, narrowSchema, schemaTypes } from '@kubb/ast'
|
|
3
|
-
import type { ArraySchemaNode, SchemaNode } from '@kubb/ast/types'
|
|
1
|
+
import { extractRefName, isStringType, narrowSchema, schemaTypes, syncSchemaRef } from '@kubb/ast'
|
|
4
2
|
import type { PrinterFactoryOptions } from '@kubb/core'
|
|
5
3
|
import { definePrinter } from '@kubb/core'
|
|
6
4
|
import { safePrint } from '@kubb/fabric-core/parsers/typescript'
|
|
@@ -8,6 +6,7 @@ import type ts from 'typescript'
|
|
|
8
6
|
import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from '../constants.ts'
|
|
9
7
|
import * as factory from '../factory.ts'
|
|
10
8
|
import type { PluginTs, ResolverTs } from '../types.ts'
|
|
9
|
+
import { buildPropertyJSDocComments } from '../utils.ts'
|
|
11
10
|
|
|
12
11
|
type TsOptions = {
|
|
13
12
|
/**
|
|
@@ -65,140 +64,6 @@ type TsOptions = {
|
|
|
65
64
|
*/
|
|
66
65
|
type TsPrinter = PrinterFactoryOptions<'typescript', TsOptions, ts.TypeNode, string>
|
|
67
66
|
|
|
68
|
-
/**
|
|
69
|
-
* Converts a primitive const value to a TypeScript literal type node.
|
|
70
|
-
* Handles negative numbers via a prefix unary expression.
|
|
71
|
-
*/
|
|
72
|
-
function constToTypeNode(value: string | number | boolean, format: 'string' | 'number' | 'boolean'): ts.TypeNode | undefined {
|
|
73
|
-
if (format === 'boolean') {
|
|
74
|
-
return factory.createLiteralTypeNode(value === true ? factory.createTrue() : factory.createFalse())
|
|
75
|
-
}
|
|
76
|
-
if (format === 'number' && typeof value === 'number') {
|
|
77
|
-
if (value < 0) {
|
|
78
|
-
return factory.createLiteralTypeNode(factory.createPrefixUnaryExpression(factory.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value))))
|
|
79
|
-
}
|
|
80
|
-
return factory.createLiteralTypeNode(factory.createNumericLiteral(value))
|
|
81
|
-
}
|
|
82
|
-
return factory.createLiteralTypeNode(factory.createStringLiteral(String(value)))
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Returns a `Date` reference type node when `representation` is `'date'`, otherwise falls back to `string`.
|
|
87
|
-
*/
|
|
88
|
-
function dateOrStringNode(node: { representation?: string }): ts.TypeNode {
|
|
89
|
-
return node.representation === 'date' ? factory.createTypeReferenceNode(factory.createIdentifier('Date')) : factory.keywordTypeNodes.string
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
|
|
94
|
-
*/
|
|
95
|
-
function buildMemberNodes(members: Array<SchemaNode> | undefined, print: (node: SchemaNode) => ts.TypeNode | null | undefined): Array<ts.TypeNode> {
|
|
96
|
-
return (members ?? []).map(print).filter(Boolean)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Builds a TypeScript tuple type node from an array schema's `items`,
|
|
101
|
-
* applying min/max slice and optional/rest element rules.
|
|
102
|
-
*/
|
|
103
|
-
function buildTupleNode(node: ArraySchemaNode, print: (node: SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
|
|
104
|
-
let items = (node.items ?? []).map(print).filter(Boolean)
|
|
105
|
-
|
|
106
|
-
const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
|
|
107
|
-
const { min, max } = node
|
|
108
|
-
|
|
109
|
-
if (max !== undefined) {
|
|
110
|
-
items = items.slice(0, max)
|
|
111
|
-
if (items.length < max && restNode) {
|
|
112
|
-
items = [...items, ...Array(max - items.length).fill(restNode)]
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (min !== undefined) {
|
|
117
|
-
items = items.map((item, i) => (i >= min ? factory.createOptionalTypeNode(item) : item))
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (max === undefined && restNode) {
|
|
121
|
-
items.push(factory.createRestTypeNode(factory.createArrayTypeNode(restNode)))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return factory.createTupleTypeNode(items)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
|
|
129
|
-
*/
|
|
130
|
-
function buildPropertyType(schema: SchemaNode, baseType: ts.TypeNode, optionalType: TsOptions['optionalType']): ts.TypeNode {
|
|
131
|
-
const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType)
|
|
132
|
-
|
|
133
|
-
let type = baseType
|
|
134
|
-
|
|
135
|
-
if (schema.nullable) {
|
|
136
|
-
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if ((schema.nullish || schema.optional) && addsUndefined) {
|
|
140
|
-
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return type
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Collects JSDoc annotation strings (description, deprecated, min/max, pattern, default, example, type) for a schema node.
|
|
148
|
-
*/
|
|
149
|
-
function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefined> {
|
|
150
|
-
const isArray = schema.type === 'array'
|
|
151
|
-
|
|
152
|
-
return [
|
|
153
|
-
'description' in schema && schema.description ? `@description ${jsStringEscape(schema.description)}` : undefined,
|
|
154
|
-
'deprecated' in schema && schema.deprecated ? '@deprecated' : undefined,
|
|
155
|
-
// minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
|
|
156
|
-
!isArray && 'min' in schema && schema.min !== undefined ? `@minLength ${schema.min}` : undefined,
|
|
157
|
-
!isArray && 'max' in schema && schema.max !== undefined ? `@maxLength ${schema.max}` : undefined,
|
|
158
|
-
'pattern' in schema && schema.pattern ? `@pattern ${schema.pattern}` : undefined,
|
|
159
|
-
'default' in schema && schema.default !== undefined
|
|
160
|
-
? `@default ${'primitive' in schema && schema.primitive === 'string' ? stringify(schema.default as string) : schema.default}`
|
|
161
|
-
: undefined,
|
|
162
|
-
'example' in schema && schema.example !== undefined ? `@example ${schema.example}` : undefined,
|
|
163
|
-
'primitive' in schema && schema.primitive
|
|
164
|
-
? [`@type ${schema.primitive || 'unknown'}`, 'optional' in schema && schema.optional ? ' | undefined' : undefined].filter(Boolean).join('')
|
|
165
|
-
: undefined,
|
|
166
|
-
]
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
|
|
171
|
-
*/
|
|
172
|
-
function buildIndexSignatures(
|
|
173
|
-
node: { additionalProperties?: SchemaNode | boolean; patternProperties?: Record<string, SchemaNode> },
|
|
174
|
-
propertyCount: number,
|
|
175
|
-
print: (node: SchemaNode) => ts.TypeNode | null | undefined,
|
|
176
|
-
): Array<ts.TypeElement> {
|
|
177
|
-
const elements: Array<ts.TypeElement> = []
|
|
178
|
-
|
|
179
|
-
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
180
|
-
const additionalType = print(node.additionalProperties) ?? factory.keywordTypeNodes.unknown
|
|
181
|
-
|
|
182
|
-
elements.push(factory.createIndexSignature(propertyCount > 0 ? factory.keywordTypeNodes.unknown : additionalType))
|
|
183
|
-
} else if (node.additionalProperties === true) {
|
|
184
|
-
elements.push(factory.createIndexSignature(factory.keywordTypeNodes.unknown))
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (node.patternProperties) {
|
|
188
|
-
const first = Object.values(node.patternProperties)[0]
|
|
189
|
-
if (first) {
|
|
190
|
-
let patternType = print(first) ?? factory.keywordTypeNodes.unknown
|
|
191
|
-
|
|
192
|
-
if (first.nullable) {
|
|
193
|
-
patternType = factory.createUnionDeclaration({ nodes: [patternType, factory.keywordTypeNodes.null] })
|
|
194
|
-
}
|
|
195
|
-
elements.push(factory.createIndexSignature(patternType))
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return elements
|
|
200
|
-
}
|
|
201
|
-
|
|
202
67
|
/**
|
|
203
68
|
* TypeScript type printer built with `definePrinter`.
|
|
204
69
|
*
|
|
@@ -245,12 +110,14 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
245
110
|
}
|
|
246
111
|
return factory.keywordTypeNodes.string
|
|
247
112
|
},
|
|
113
|
+
ipv4: () => factory.keywordTypeNodes.string,
|
|
114
|
+
ipv6: () => factory.keywordTypeNodes.string,
|
|
248
115
|
datetime: () => factory.keywordTypeNodes.string,
|
|
249
116
|
number: () => factory.keywordTypeNodes.number,
|
|
250
117
|
integer: () => factory.keywordTypeNodes.number,
|
|
251
118
|
bigint: () => factory.keywordTypeNodes.bigint,
|
|
252
|
-
date: dateOrStringNode,
|
|
253
|
-
time: dateOrStringNode,
|
|
119
|
+
date: factory.dateOrStringNode,
|
|
120
|
+
time: factory.dateOrStringNode,
|
|
254
121
|
ref(node) {
|
|
255
122
|
if (!node.name) {
|
|
256
123
|
return undefined
|
|
@@ -259,7 +126,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
259
126
|
// Use the canonical name from the $ref path — node.name may have been overridden
|
|
260
127
|
// (e.g. by single-member allOf flatten using the property-derived child name).
|
|
261
128
|
// Inline refs (without $ref) from utils already carry resolved type names.
|
|
262
|
-
const refName = node.ref ? (node.ref
|
|
129
|
+
const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
|
|
263
130
|
|
|
264
131
|
// When a Key suffix is configured, enum refs must use the suffixed name (e.g. `StatusKey`)
|
|
265
132
|
// so the reference matches what the enum file actually exports.
|
|
@@ -267,7 +134,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
267
134
|
node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName)
|
|
268
135
|
|
|
269
136
|
const name = isEnumRef
|
|
270
|
-
? this.options.resolver.resolveEnumKeyName({ name: refName }
|
|
137
|
+
? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix!)
|
|
271
138
|
: node.ref
|
|
272
139
|
? this.options.resolver.default(refName, 'type')
|
|
273
140
|
: refName
|
|
@@ -279,8 +146,8 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
279
146
|
|
|
280
147
|
if (this.options.enumType === 'inlineLiteral' || !node.name) {
|
|
281
148
|
const literalNodes = values
|
|
282
|
-
.filter((v): v is string | number | boolean => v !== null)
|
|
283
|
-
.map((value) => constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
|
|
149
|
+
.filter((v): v is string | number | boolean => v !== null && v !== undefined)
|
|
150
|
+
.map((value) => factory.constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
|
|
284
151
|
.filter(Boolean)
|
|
285
152
|
|
|
286
153
|
return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
|
|
@@ -288,7 +155,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
288
155
|
|
|
289
156
|
const resolvedName =
|
|
290
157
|
ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix
|
|
291
|
-
? this.options.resolver.resolveEnumKeyName(node
|
|
158
|
+
? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix)
|
|
292
159
|
: this.options.resolver.default(node.name, 'type')
|
|
293
160
|
|
|
294
161
|
return factory.createTypeReferenceNode(resolvedName, undefined)
|
|
@@ -319,10 +186,10 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
319
186
|
return factory.createUnionDeclaration({ withParentheses: true, nodes: memberNodes }) ?? undefined
|
|
320
187
|
}
|
|
321
188
|
|
|
322
|
-
return factory.createUnionDeclaration({ withParentheses: true, nodes: buildMemberNodes(members, this.transform) }) ?? undefined
|
|
189
|
+
return factory.createUnionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(members, this.transform) }) ?? undefined
|
|
323
190
|
},
|
|
324
191
|
intersection(node) {
|
|
325
|
-
return factory.createIntersectionDeclaration({ withParentheses: true, nodes: buildMemberNodes(node.members, this.transform) }) ?? undefined
|
|
192
|
+
return factory.createIntersectionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(node.members, this.transform) }) ?? undefined
|
|
326
193
|
},
|
|
327
194
|
array(node) {
|
|
328
195
|
const itemNodes = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
@@ -330,7 +197,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
330
197
|
return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? undefined
|
|
331
198
|
},
|
|
332
199
|
tuple(node) {
|
|
333
|
-
return buildTupleNode(node, this.transform)
|
|
200
|
+
return factory.buildTupleNode(node, this.transform)
|
|
334
201
|
},
|
|
335
202
|
object(node) {
|
|
336
203
|
const { transform, options } = this
|
|
@@ -339,19 +206,20 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
339
206
|
|
|
340
207
|
const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
|
|
341
208
|
const baseType = transform(prop.schema) ?? factory.keywordTypeNodes.unknown
|
|
342
|
-
const type = buildPropertyType(prop.schema, baseType, options.optionalType)
|
|
209
|
+
const type = factory.buildPropertyType(prop.schema, baseType, options.optionalType)
|
|
210
|
+
const propMeta = syncSchemaRef(prop.schema)
|
|
343
211
|
|
|
344
212
|
const propertyNode = factory.createPropertySignature({
|
|
345
213
|
questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
|
|
346
214
|
name: prop.name,
|
|
347
215
|
type,
|
|
348
|
-
readOnly:
|
|
216
|
+
readOnly: propMeta?.readOnly,
|
|
349
217
|
})
|
|
350
218
|
|
|
351
219
|
return factory.appendJSDocToNode({ node: propertyNode, comments: buildPropertyJSDocComments(prop.schema) })
|
|
352
220
|
})
|
|
353
221
|
|
|
354
|
-
const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)]
|
|
222
|
+
const allElements = [...propertyNodes, ...factory.buildIndexSignatures(node, propertyNodes.length, transform)]
|
|
355
223
|
|
|
356
224
|
if (!allElements.length) {
|
|
357
225
|
return factory.keywordTypeNodes.object
|
|
@@ -361,50 +229,50 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
|
361
229
|
},
|
|
362
230
|
},
|
|
363
231
|
print(node) {
|
|
364
|
-
|
|
232
|
+
const { name, syntaxType = 'type', description, keysToOmit } = this.options
|
|
365
233
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
234
|
+
let base = this.transform(node)
|
|
235
|
+
if (!base) return null
|
|
236
|
+
|
|
237
|
+
// For ref nodes, structural metadata lives on node.schema rather than the ref node itself.
|
|
238
|
+
const meta = syncSchemaRef(node)
|
|
369
239
|
|
|
370
|
-
//
|
|
371
|
-
if (
|
|
372
|
-
|
|
240
|
+
// Without name, apply modifiers inline and return.
|
|
241
|
+
if (!name) {
|
|
242
|
+
if (meta.nullable) {
|
|
243
|
+
base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.null] })
|
|
244
|
+
}
|
|
245
|
+
if ((meta.nullish || meta.optional) && addsUndefined) {
|
|
246
|
+
base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.undefined] })
|
|
247
|
+
}
|
|
248
|
+
return safePrint(base)
|
|
373
249
|
}
|
|
374
250
|
|
|
375
|
-
|
|
376
|
-
|
|
251
|
+
// When keysToOmit is present, wrap with Omit first, then apply nullable/optional
|
|
252
|
+
// modifiers so they are not swallowed by NonNullable inside createOmitDeclaration.
|
|
253
|
+
let inner: ts.TypeNode = keysToOmit?.length ? factory.createOmitDeclaration({ keys: keysToOmit, type: base, nonNullable: true }) : base
|
|
254
|
+
|
|
255
|
+
if (meta.nullable) {
|
|
256
|
+
inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.null] })
|
|
377
257
|
}
|
|
378
258
|
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
if (
|
|
382
|
-
|
|
259
|
+
// For named type declarations (type aliases), optional/nullish always produces | undefined
|
|
260
|
+
// regardless of optionalType — the questionToken ? modifier only applies to object properties.
|
|
261
|
+
if (meta.nullish || meta.optional) {
|
|
262
|
+
inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.undefined] })
|
|
383
263
|
}
|
|
384
264
|
|
|
385
|
-
const useTypeGeneration = syntaxType === 'type' ||
|
|
265
|
+
const useTypeGeneration = syntaxType === 'type' || inner.kind === factory.syntaxKind.union || !!keysToOmit?.length
|
|
386
266
|
|
|
387
267
|
const typeNode = factory.createTypeDeclaration({
|
|
388
268
|
name,
|
|
389
269
|
isExportable: true,
|
|
390
|
-
type:
|
|
391
|
-
? factory.createOmitDeclaration({
|
|
392
|
-
keys: keysToOmit,
|
|
393
|
-
type,
|
|
394
|
-
nonNullable: true,
|
|
395
|
-
})
|
|
396
|
-
: type,
|
|
270
|
+
type: inner,
|
|
397
271
|
syntax: useTypeGeneration ? 'type' : 'interface',
|
|
398
|
-
comments:
|
|
399
|
-
|
|
400
|
-
description
|
|
401
|
-
|
|
402
|
-
node && 'min' in node && node.min !== undefined ? `@minLength ${node.min}` : undefined,
|
|
403
|
-
node && 'max' in node && node.max !== undefined ? `@maxLength ${node.max}` : undefined,
|
|
404
|
-
node && 'pattern' in node && node.pattern ? `@pattern ${node.pattern}` : undefined,
|
|
405
|
-
node?.default ? `@default ${node.default}` : undefined,
|
|
406
|
-
node?.example ? `@example ${node.example}` : undefined,
|
|
407
|
-
],
|
|
272
|
+
comments: buildPropertyJSDocComments({
|
|
273
|
+
...meta,
|
|
274
|
+
description,
|
|
275
|
+
}),
|
|
408
276
|
})
|
|
409
277
|
|
|
410
278
|
return safePrint(typeNode)
|
|
@@ -2,7 +2,7 @@ import { pascalCase } from '@internals/utils'
|
|
|
2
2
|
import { defineResolver } from '@kubb/core'
|
|
3
3
|
import type { PluginTs } from '../types.ts'
|
|
4
4
|
|
|
5
|
-
function
|
|
5
|
+
function toTypeName(name: string, type?: 'file' | 'function' | 'type' | 'const'): string {
|
|
6
6
|
return pascalCase(name, { isFile: type === 'file' })
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -28,7 +28,7 @@ export const resolverTs = defineResolver<PluginTs>(() => {
|
|
|
28
28
|
name: 'default',
|
|
29
29
|
pluginName: 'plugin-ts',
|
|
30
30
|
default(name, type) {
|
|
31
|
-
return
|
|
31
|
+
return toTypeName(name, type)
|
|
32
32
|
},
|
|
33
33
|
resolveName(name) {
|
|
34
34
|
return this.default(name, 'function')
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OperationParamsResolver } from '@kubb/ast'
|
|
2
|
-
import type { OperationNode, ParameterNode,
|
|
2
|
+
import type { OperationNode, ParameterNode, StatusCode, Visitor } from '@kubb/ast/types'
|
|
3
3
|
import type {
|
|
4
4
|
CompatibilityPreset,
|
|
5
5
|
Exclude,
|
|
@@ -35,7 +35,9 @@ export type ResolverTs = Resolver &
|
|
|
35
35
|
* resolver.resolvePathName('list pets', 'file') // → 'ListPets'
|
|
36
36
|
*/
|
|
37
37
|
resolvePathName(name: string, type?: 'file' | 'function' | 'type' | 'const'): string
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Resolves the request body type name for an operation (required on ResolverTs).
|
|
40
|
+
*/
|
|
39
41
|
resolveDataName(node: OperationNode): string
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -76,7 +78,7 @@ export type ResolverTs = Resolver &
|
|
|
76
78
|
* resolver.resolveEnumKeyName(node, 'Value') // → 'PetStatusValue'
|
|
77
79
|
* resolver.resolveEnumKeyName(node, '') // → 'PetStatus'
|
|
78
80
|
*/
|
|
79
|
-
resolveEnumKeyName(node:
|
|
81
|
+
resolveEnumKeyName(node: { name?: string | null }, enumTypeSuffix: string): string
|
|
80
82
|
/**
|
|
81
83
|
* Resolves the name for an operation's grouped path parameters type.
|
|
82
84
|
*
|
|
@@ -169,7 +171,7 @@ type EnumTypeOptions =
|
|
|
169
171
|
/**
|
|
170
172
|
* Choose to use enum, asConst, asPascalConst, constEnum, literal, or inlineLiteral for enums.
|
|
171
173
|
* - 'literal' generates literal union types.
|
|
172
|
-
* - 'inlineLiteral'
|
|
174
|
+
* - 'inlineLiteral' will inline enum values directly into the type (default in v5).
|
|
173
175
|
* @default 'asConst'
|
|
174
176
|
* @note In Kubb v5, 'inlineLiteral' becomes the default.
|
|
175
177
|
*/
|
package/src/utils.ts
CHANGED
|
@@ -1,19 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsStringEscape, stringify } from '@internals/utils'
|
|
2
|
+
import { createProperty, createSchema, syncSchemaRef } from '@kubb/ast'
|
|
2
3
|
import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
|
|
3
4
|
import type { ResolverTs } from './types.ts'
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Collects JSDoc annotation strings for a schema node.
|
|
8
|
+
*
|
|
9
|
+
* Only uses official JSDoc tags from https://jsdoc.app/: `@description`, `@deprecated`, `@default`, `@example`, `@type`.
|
|
10
|
+
* Constraint metadata (min/max length, pattern, multipleOf, min/maxProperties) is emitted as plain-text lines.
|
|
11
|
+
|
|
12
|
+
*/
|
|
13
|
+
export function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefined> {
|
|
14
|
+
const meta = syncSchemaRef(schema)
|
|
15
|
+
|
|
16
|
+
const isArray = meta?.primitive === 'array'
|
|
17
|
+
|
|
18
|
+
return [
|
|
19
|
+
meta && 'description' in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : undefined,
|
|
20
|
+
meta && 'deprecated' in meta && meta.deprecated ? '@deprecated' : undefined,
|
|
21
|
+
// minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
|
|
22
|
+
!isArray && meta && 'min' in meta && meta.min !== undefined ? `@minLength ${meta.min}` : undefined,
|
|
23
|
+
!isArray && meta && 'max' in meta && meta.max !== undefined ? `@maxLength ${meta.max}` : undefined,
|
|
24
|
+
meta && 'pattern' in meta && meta.pattern ? `@pattern ${meta.pattern}` : undefined,
|
|
25
|
+
meta && 'default' in meta && meta.default !== undefined
|
|
26
|
+
? `@default ${'primitive' in meta && meta.primitive === 'string' ? stringify(meta.default as string) : meta.default}`
|
|
27
|
+
: undefined,
|
|
28
|
+
meta && 'example' in meta && meta.example !== undefined ? `@example ${meta.example}` : undefined,
|
|
29
|
+
meta && 'primitive' in meta && meta.primitive
|
|
30
|
+
? [`@type ${meta.primitive}`, 'optional' in schema && schema.optional ? ' | undefined' : undefined].filter(Boolean).join('')
|
|
31
|
+
: undefined,
|
|
32
|
+
].filter(Boolean)
|
|
33
|
+
}
|
|
34
|
+
|
|
5
35
|
type BuildParamsSchemaOptions = {
|
|
6
36
|
params: Array<ParameterNode>
|
|
7
|
-
node: OperationNode
|
|
8
37
|
resolver: ResolverTs
|
|
9
38
|
}
|
|
10
39
|
|
|
11
40
|
type BuildOperationSchemaOptions = {
|
|
12
|
-
node: OperationNode
|
|
13
41
|
resolver: ResolverTs
|
|
14
42
|
}
|
|
15
43
|
|
|
16
|
-
export function buildParams({ params,
|
|
44
|
+
export function buildParams(node: OperationNode, { params, resolver }: BuildParamsSchemaOptions): SchemaNode {
|
|
17
45
|
return createSchema({
|
|
18
46
|
type: 'object',
|
|
19
47
|
properties: params.map((param) =>
|
|
@@ -29,7 +57,7 @@ export function buildParams({ params, node, resolver }: BuildParamsSchemaOptions
|
|
|
29
57
|
})
|
|
30
58
|
}
|
|
31
59
|
|
|
32
|
-
export function buildData(
|
|
60
|
+
export function buildData(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode {
|
|
33
61
|
const pathParams = node.parameters.filter((p) => p.in === 'path')
|
|
34
62
|
const queryParams = node.parameters.filter((p) => p.in === 'query')
|
|
35
63
|
const headerParams = node.parameters.filter((p) => p.in === 'header')
|
|
@@ -42,26 +70,26 @@ export function buildData({ node, resolver }: BuildOperationSchemaOptions): Sche
|
|
|
42
70
|
name: 'data',
|
|
43
71
|
schema: node.requestBody?.schema
|
|
44
72
|
? createSchema({ type: 'ref', name: resolver.resolveDataName(node), optional: true })
|
|
45
|
-
: createSchema({ type: 'never', optional: true }),
|
|
73
|
+
: createSchema({ type: 'never', primitive: undefined, optional: true }),
|
|
46
74
|
}),
|
|
47
75
|
createProperty({
|
|
48
76
|
name: 'pathParams',
|
|
49
77
|
required: pathParams.length > 0,
|
|
50
|
-
schema: pathParams.length > 0 ? buildParams({ params: pathParams,
|
|
78
|
+
schema: pathParams.length > 0 ? buildParams(node, { params: pathParams, resolver }) : createSchema({ type: 'never', primitive: undefined }),
|
|
51
79
|
}),
|
|
52
80
|
createProperty({
|
|
53
81
|
name: 'queryParams',
|
|
54
82
|
schema:
|
|
55
83
|
queryParams.length > 0
|
|
56
|
-
? createSchema({ ...buildParams({ params: queryParams,
|
|
57
|
-
: createSchema({ type: 'never', optional: true }),
|
|
84
|
+
? createSchema({ ...buildParams(node, { params: queryParams, resolver }), optional: true })
|
|
85
|
+
: createSchema({ type: 'never', primitive: undefined, optional: true }),
|
|
58
86
|
}),
|
|
59
87
|
createProperty({
|
|
60
88
|
name: 'headerParams',
|
|
61
89
|
schema:
|
|
62
90
|
headerParams.length > 0
|
|
63
|
-
? createSchema({ ...buildParams({ params: headerParams,
|
|
64
|
-
: createSchema({ type: 'never', optional: true }),
|
|
91
|
+
? createSchema({ ...buildParams(node, { params: headerParams, resolver }), optional: true })
|
|
92
|
+
: createSchema({ type: 'never', primitive: undefined, optional: true }),
|
|
65
93
|
}),
|
|
66
94
|
createProperty({
|
|
67
95
|
name: 'url',
|
|
@@ -72,7 +100,7 @@ export function buildData({ node, resolver }: BuildOperationSchemaOptions): Sche
|
|
|
72
100
|
})
|
|
73
101
|
}
|
|
74
102
|
|
|
75
|
-
export function buildResponses(
|
|
103
|
+
export function buildResponses(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode | null {
|
|
76
104
|
if (node.responses.length === 0) {
|
|
77
105
|
return null
|
|
78
106
|
}
|
|
@@ -89,7 +117,7 @@ export function buildResponses({ node, resolver }: BuildOperationSchemaOptions):
|
|
|
89
117
|
})
|
|
90
118
|
}
|
|
91
119
|
|
|
92
|
-
export function buildResponseUnion(
|
|
120
|
+
export function buildResponseUnion(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode | null {
|
|
93
121
|
const responsesWithSchema = node.responses.filter((res) => res.schema)
|
|
94
122
|
|
|
95
123
|
if (responsesWithSchema.length === 0) {
|