@kubb/plugin-ts 5.0.0-alpha.8 → 5.0.0-beta.3

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/LICENSE +17 -10
  2. package/README.md +1 -3
  3. package/dist/index.cjs +1452 -4
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +574 -4
  6. package/dist/index.js +1418 -2
  7. package/dist/index.js.map +1 -0
  8. package/package.json +44 -64
  9. package/src/components/{v2/Enum.tsx → Enum.tsx} +33 -17
  10. package/src/components/Type.tsx +31 -161
  11. package/src/constants.ts +15 -5
  12. package/src/factory.ts +283 -35
  13. package/src/generators/typeGenerator.tsx +189 -424
  14. package/src/index.ts +9 -2
  15. package/src/plugin.ts +67 -205
  16. package/src/printers/functionPrinter.ts +197 -0
  17. package/src/printers/printerTs.ts +325 -0
  18. package/src/resolvers/resolverTs.ts +66 -0
  19. package/src/types.ts +238 -94
  20. package/src/utils.ts +130 -0
  21. package/dist/components-CRu8IKY3.js +0 -729
  22. package/dist/components-CRu8IKY3.js.map +0 -1
  23. package/dist/components-DeNDKlzf.cjs +0 -982
  24. package/dist/components-DeNDKlzf.cjs.map +0 -1
  25. package/dist/components.cjs +0 -3
  26. package/dist/components.d.ts +0 -36
  27. package/dist/components.js +0 -2
  28. package/dist/generators.cjs +0 -4
  29. package/dist/generators.d.ts +0 -480
  30. package/dist/generators.js +0 -2
  31. package/dist/plugin-D5NGPj0v.js +0 -1232
  32. package/dist/plugin-D5NGPj0v.js.map +0 -1
  33. package/dist/plugin-MLTxoa8p.cjs +0 -1279
  34. package/dist/plugin-MLTxoa8p.cjs.map +0 -1
  35. package/dist/types-CsvB6X5Y.d.ts +0 -167
  36. package/src/components/index.ts +0 -1
  37. package/src/components/v2/Type.tsx +0 -59
  38. package/src/generators/index.ts +0 -2
  39. package/src/generators/v2/typeGenerator.tsx +0 -171
  40. package/src/generators/v2/utils.ts +0 -140
  41. package/src/parser.ts +0 -389
  42. package/src/printer.ts +0 -368
package/src/printer.ts DELETED
@@ -1,368 +0,0 @@
1
- import { jsStringEscape, pascalCase, stringify } from '@internals/utils'
2
- import { isPlainStringType } from '@kubb/ast'
3
- import type { ArraySchemaNode, SchemaNode } from '@kubb/ast/types'
4
- import type { PrinterFactoryOptions } from '@kubb/core'
5
- import { definePrinter } from '@kubb/core'
6
- import type ts from 'typescript'
7
- import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
8
- import * as factory from './factory.ts'
9
- import type { PluginTs } from './types.ts'
10
-
11
- type TsOptions = {
12
- /**
13
- * @default `'questionToken'`
14
- */
15
- optionalType: PluginTs['resolvedOptions']['optionalType']
16
- /**
17
- * @default `'array'`
18
- */
19
- arrayType: PluginTs['resolvedOptions']['arrayType']
20
- /**
21
- * @default `'inlineLiteral'`
22
- */
23
- enumType: PluginTs['resolvedOptions']['enumType']
24
- /**
25
- * Controls whether a `type` alias or `interface` declaration is emitted.
26
- * @default `'type'`
27
- */
28
- syntaxType?: PluginTs['resolvedOptions']['syntaxType']
29
- /**
30
- * When set, `printer.print(node)` produces a full `type Name = …` declaration.
31
- * When omitted, `printer.print(node)` returns the raw type node.
32
- */
33
- typeName?: string
34
-
35
- /**
36
- * JSDoc `@description` comment added to the generated type or interface declaration.
37
- */
38
- description?: string
39
- /**
40
- * Property keys to exclude from the generated type via `Omit<Type, Keys>`.
41
- * Forces type-alias syntax even when `syntaxType` is `'interface'`.
42
- */
43
- keysToOmit?: Array<string>
44
- }
45
-
46
- /**
47
- * TypeScript printer factory options: maps `SchemaNode` → `ts.TypeNode` (raw) or `ts.Node` (full declaration).
48
- */
49
- type TsPrinter = PrinterFactoryOptions<'typescript', TsOptions, ts.TypeNode, ts.Node>
50
-
51
- /**
52
- * Converts a primitive const value to a TypeScript literal type node.
53
- * Handles negative numbers via a prefix unary expression.
54
- */
55
- function constToTypeNode(value: string | number | boolean, format: 'string' | 'number' | 'boolean'): ts.TypeNode | undefined {
56
- if (format === 'boolean') {
57
- return factory.createLiteralTypeNode(value === true ? factory.createTrue() : factory.createFalse())
58
- }
59
- if (format === 'number' && typeof value === 'number') {
60
- if (value < 0) {
61
- return factory.createLiteralTypeNode(factory.createPrefixUnaryExpression(factory.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value))))
62
- }
63
- return factory.createLiteralTypeNode(factory.createNumericLiteral(value))
64
- }
65
- return factory.createLiteralTypeNode(factory.createStringLiteral(String(value)))
66
- }
67
-
68
- /**
69
- * Returns a `Date` reference type node when `representation` is `'date'`, otherwise falls back to `string`.
70
- */
71
- function dateOrStringNode(node: { representation?: string }): ts.TypeNode {
72
- return node.representation === 'date' ? factory.createTypeReferenceNode(factory.createIdentifier('Date')) : factory.keywordTypeNodes.string
73
- }
74
-
75
- /**
76
- * Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
77
- */
78
- function buildMemberNodes(members: Array<SchemaNode> | undefined, print: (node: SchemaNode) => ts.TypeNode | null | undefined): Array<ts.TypeNode> {
79
- return (members ?? []).map(print).filter(Boolean)
80
- }
81
-
82
- /**
83
- * Builds a TypeScript tuple type node from an array schema's `items`,
84
- * applying min/max slice and optional/rest element rules.
85
- */
86
- function buildTupleNode(node: ArraySchemaNode, print: (node: SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
87
- let items = (node.items ?? []).map(print).filter(Boolean)
88
-
89
- const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
90
- const { min, max } = node
91
-
92
- if (max !== undefined) {
93
- items = items.slice(0, max)
94
- if (items.length < max && restNode) {
95
- items = [...items, ...Array(max - items.length).fill(restNode)]
96
- }
97
- }
98
-
99
- if (min !== undefined) {
100
- items = items.map((item, i) => (i >= min ? factory.createOptionalTypeNode(item) : item))
101
- }
102
-
103
- if (max === undefined && restNode) {
104
- items.push(factory.createRestTypeNode(factory.createArrayTypeNode(restNode)))
105
- }
106
-
107
- return factory.createTupleTypeNode(items)
108
- }
109
-
110
- /**
111
- * Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
112
- */
113
- function buildPropertyType(schema: SchemaNode, baseType: ts.TypeNode, optionalType: TsOptions['optionalType']): ts.TypeNode {
114
- const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType)
115
-
116
- let type = baseType
117
-
118
- if (schema.nullable) {
119
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
120
- }
121
-
122
- if ((schema.nullish || schema.optional) && addsUndefined) {
123
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
124
- }
125
-
126
- return type
127
- }
128
-
129
- /**
130
- * Collects JSDoc annotation strings (description, deprecated, min/max, pattern, default, example, type) for a schema node.
131
- */
132
- function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefined> {
133
- return [
134
- 'description' in schema && schema.description ? `@description ${jsStringEscape(schema.description)}` : undefined,
135
- 'deprecated' in schema && schema.deprecated ? '@deprecated' : undefined,
136
- 'min' in schema && schema.min !== undefined ? `@minLength ${schema.min}` : undefined,
137
- 'max' in schema && schema.max !== undefined ? `@maxLength ${schema.max}` : undefined,
138
- 'pattern' in schema && schema.pattern ? `@pattern ${schema.pattern}` : undefined,
139
- 'default' in schema && schema.default !== undefined
140
- ? `@default ${'primitive' in schema && schema.primitive === 'string' ? stringify(schema.default as string) : schema.default}`
141
- : undefined,
142
- 'example' in schema && schema.example !== undefined ? `@example ${schema.example}` : undefined,
143
- 'primitive' in schema && schema.primitive
144
- ? [`@type ${schema.primitive || 'unknown'}`, 'optional' in schema && schema.optional ? ' | undefined' : undefined].filter(Boolean).join('')
145
- : undefined,
146
- ]
147
- }
148
-
149
- /**
150
- * Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
151
- */
152
- function buildIndexSignatures(
153
- node: { additionalProperties?: SchemaNode | boolean; patternProperties?: Record<string, SchemaNode> },
154
- propertyCount: number,
155
- print: (node: SchemaNode) => ts.TypeNode | null | undefined,
156
- ): Array<ts.TypeElement> {
157
- const elements: Array<ts.TypeElement> = []
158
-
159
- if (node.additionalProperties && node.additionalProperties !== true) {
160
- const additionalType = print(node.additionalProperties) ?? factory.keywordTypeNodes.unknown
161
-
162
- elements.push(factory.createIndexSignature(propertyCount > 0 ? factory.keywordTypeNodes.unknown : additionalType))
163
- } else if (node.additionalProperties === true) {
164
- elements.push(factory.createIndexSignature(factory.keywordTypeNodes.unknown))
165
- }
166
-
167
- if (node.patternProperties) {
168
- const first = Object.values(node.patternProperties)[0]
169
- if (first) {
170
- let patternType = print(first) ?? factory.keywordTypeNodes.unknown
171
-
172
- if (first.nullable) {
173
- patternType = factory.createUnionDeclaration({ nodes: [patternType, factory.keywordTypeNodes.null] })
174
- }
175
- elements.push(factory.createIndexSignature(patternType))
176
- }
177
- }
178
-
179
- return elements
180
- }
181
-
182
- /**
183
- * TypeScript type printer built with `definePrinter`.
184
- *
185
- * Converts a `SchemaNode` AST node into a TypeScript AST node:
186
- * - **`printer.print(node)`** — when `options.typeName` is set, returns a full
187
- * `type Name = …` or `interface Name { … }` declaration (`ts.Node`).
188
- * Without `typeName`, returns the raw `ts.TypeNode` for the schema.
189
- *
190
- * Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
191
- * over per printer instance, so each call to `printerTs(options)` produces an independent printer.
192
- *
193
- * @example Raw type node (no `typeName`)
194
- * ```ts
195
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
196
- * const typeNode = printer.print(schemaNode) // ts.TypeNode
197
- * ```
198
- *
199
- * @example Full declaration (with `typeName`)
200
- * ```ts
201
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
202
- * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
203
- * ```
204
- */
205
- export const printerTs = definePrinter<TsPrinter>((options) => {
206
- const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(options.optionalType)
207
-
208
- return {
209
- name: 'typescript',
210
- options,
211
- nodes: {
212
- any: () => factory.keywordTypeNodes.any,
213
- unknown: () => factory.keywordTypeNodes.unknown,
214
- void: () => factory.keywordTypeNodes.void,
215
- never: () => factory.keywordTypeNodes.never,
216
- boolean: () => factory.keywordTypeNodes.boolean,
217
- null: () => factory.keywordTypeNodes.null,
218
- blob: () => factory.createTypeReferenceNode('Blob', []),
219
- string: () => factory.keywordTypeNodes.string,
220
- uuid: () => factory.keywordTypeNodes.string,
221
- email: () => factory.keywordTypeNodes.string,
222
- url: (node) => {
223
- if (node.path) {
224
- return factory.createUrlTemplateType(node.path)
225
- }
226
- return factory.keywordTypeNodes.string
227
- },
228
- datetime: () => factory.keywordTypeNodes.string,
229
- number: () => factory.keywordTypeNodes.number,
230
- integer: () => factory.keywordTypeNodes.number,
231
- bigint: () => factory.keywordTypeNodes.bigint,
232
- date: dateOrStringNode,
233
- time: dateOrStringNode,
234
- ref(node) {
235
- if (!node.name) {
236
- return undefined
237
- }
238
- return factory.createTypeReferenceNode(node.name, undefined)
239
- },
240
- enum(node) {
241
- const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
242
-
243
- if (this.options.enumType === 'inlineLiteral' || !node.name) {
244
- const literalNodes = values
245
- .filter((v): v is string | number | boolean => v !== null)
246
- .map((value) => constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
247
- .filter(Boolean)
248
-
249
- return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
250
- }
251
-
252
- const resolvedName = pascalCase(node.name)
253
- const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) ? `${resolvedName}Key` : resolvedName
254
-
255
- return factory.createTypeReferenceNode(typeName, undefined)
256
- },
257
- union(node) {
258
- const members = node.members ?? []
259
-
260
- const hasStringLiteral = members.some((m) => m.type === 'enum' && m.enumType === 'string')
261
- const hasPlainString = members.some((m) => isPlainStringType(m))
262
-
263
- if (hasStringLiteral && hasPlainString) {
264
- const memberNodes = members
265
- .map((m) => {
266
- if (isPlainStringType(m)) {
267
- return factory.createIntersectionDeclaration({
268
- nodes: [factory.keywordTypeNodes.string, factory.createTypeLiteralNode([])],
269
- withParentheses: true,
270
- })
271
- }
272
-
273
- return this.print(m)
274
- })
275
- .filter(Boolean)
276
-
277
- return factory.createUnionDeclaration({ withParentheses: true, nodes: memberNodes }) ?? undefined
278
- }
279
-
280
- return factory.createUnionDeclaration({ withParentheses: true, nodes: buildMemberNodes(members, this.print) }) ?? undefined
281
- },
282
- intersection(node) {
283
- return factory.createIntersectionDeclaration({ withParentheses: true, nodes: buildMemberNodes(node.members, this.print) }) ?? undefined
284
- },
285
- array(node) {
286
- const itemNodes = (node.items ?? []).map((item) => this.print(item)).filter(Boolean)
287
-
288
- return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? undefined
289
- },
290
- tuple(node) {
291
- return buildTupleNode(node, this.print)
292
- },
293
- object(node) {
294
- const { print, options } = this
295
- const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType)
296
-
297
- const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
298
- const baseType = print(prop.schema) ?? factory.keywordTypeNodes.unknown
299
- const type = buildPropertyType(prop.schema, baseType, options.optionalType)
300
-
301
- const propertyNode = factory.createPropertySignature({
302
- questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
303
- name: prop.name,
304
- type,
305
- readOnly: prop.schema.readOnly,
306
- })
307
-
308
- return factory.appendJSDocToNode({ node: propertyNode, comments: buildPropertyJSDocComments(prop.schema) })
309
- })
310
-
311
- const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, print)]
312
-
313
- if (!allElements.length) {
314
- return factory.keywordTypeNodes.object
315
- }
316
-
317
- return factory.createTypeLiteralNode(allElements)
318
- },
319
- },
320
- print(node) {
321
- let type = this.print(node)
322
-
323
- if (!type) {
324
- return undefined
325
- }
326
-
327
- // Apply top-level nullable / optional union modifiers.
328
- if (node.nullable) {
329
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
330
- }
331
-
332
- if ((node.nullish || node.optional) && addsUndefined) {
333
- type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
334
- }
335
-
336
- // Without typeName, return the type node as-is (no declaration wrapping).
337
- const { typeName, syntaxType = 'type', description, keysToOmit } = this.options
338
- if (!typeName) {
339
- return type
340
- }
341
-
342
- const useTypeGeneration = syntaxType === 'type' || type.kind === factory.syntaxKind.union || !!keysToOmit?.length
343
-
344
- return factory.createTypeDeclaration({
345
- name: typeName,
346
- isExportable: true,
347
- type: keysToOmit?.length
348
- ? factory.createOmitDeclaration({
349
- keys: keysToOmit,
350
- type,
351
- nonNullable: true,
352
- })
353
- : type,
354
- syntax: useTypeGeneration ? 'type' : 'interface',
355
- comments: [
356
- node?.title ? jsStringEscape(node.title) : undefined,
357
- description ? `@description ${jsStringEscape(description)}` : undefined,
358
- node?.deprecated ? '@deprecated' : undefined,
359
- node && 'min' in node && node.min !== undefined ? `@minLength ${node.min}` : undefined,
360
- node && 'max' in node && node.max !== undefined ? `@maxLength ${node.max}` : undefined,
361
- node && 'pattern' in node && node.pattern ? `@pattern ${node.pattern}` : undefined,
362
- node?.default ? `@default ${node.default}` : undefined,
363
- node?.example ? `@example ${node.example}` : undefined,
364
- ],
365
- })
366
- },
367
- }
368
- })