@kubb/plugin-ts 5.0.0-beta.42 → 5.0.0-beta.64

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.
@@ -1,326 +0,0 @@
1
- import { ast } from '@kubb/core'
2
- import { parserTs } from '@kubb/parser-ts'
3
- import type ts from 'typescript'
4
- import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from '../constants.ts'
5
- import * as factory from '../factory.ts'
6
- import type { PluginTs, ResolverTs } from '../types.ts'
7
- import { buildPropertyJSDocComments } from '../utils.ts'
8
-
9
- function isNonNullable<T>(value: T | null | undefined): value is T {
10
- return value !== null && value !== undefined
11
- }
12
-
13
- /**
14
- * Partial map of node-type overrides for the TypeScript printer.
15
- *
16
- * Each key is a `SchemaType` string (e.g. `'date'`, `'string'`). The function
17
- * replaces the built-in handler for that node type. Use `this.transform` to
18
- * recurse into nested schema nodes, and `this.options` to read printer options.
19
- *
20
- * @example Override the `date` handler
21
- * ```ts
22
- * pluginTs({
23
- * printer: {
24
- * nodes: {
25
- * date(node) {
26
- * return ts.factory.createTypeReferenceNode('Date', [])
27
- * },
28
- * },
29
- * },
30
- * })
31
- * ```
32
- */
33
- export type PrinterTsNodes = ast.PrinterPartial<ts.TypeNode, PrinterTsOptions>
34
-
35
- export type PrinterTsOptions = {
36
- /**
37
- * Mark parameters as optional with `?` or `| undefined`.
38
- * - `'questionToken'` adds `?` to properties
39
- * - `'undefined'` adds `| undefined` to types
40
- *
41
- * @default `'questionToken'`
42
- */
43
- optionalType: PluginTs['resolvedOptions']['optionalType']
44
- /**
45
- * Array representation style.
46
- * - `'array'` uses bracket notation (`T[]`)
47
- * - `'generic'` uses generic syntax (`Array<T>`)
48
- *
49
- * @default `'array'`
50
- */
51
- arrayType: PluginTs['resolvedOptions']['arrayType']
52
- /**
53
- * Enum output format.
54
- * - `'inlineLiteral'` embeds literal unions inline
55
- * - `'asPascalConst'` generates named const unions
56
- * - `'asConst'` generates as const declarations
57
- *
58
- * @default `'inlineLiteral'`
59
- */
60
- enumType: PluginTs['resolvedOptions']['enumType']
61
- /**
62
- * Suffix appended to enum key reference names.
63
- *
64
- * @example Enum key naming
65
- * `StatusKey` when `enumType` is `'asConst'`
66
- *
67
- * @default `'Key'`
68
- */
69
- enumTypeSuffix?: PluginTs['resolvedOptions']['enumTypeSuffix']
70
- /**
71
- * Syntax for generated declarations.
72
- * - `'type'` generates type aliases
73
- * - `'interface'` generates interface declarations
74
- *
75
- * @default `'type'`
76
- */
77
- syntaxType?: PluginTs['resolvedOptions']['syntaxType']
78
- /**
79
- * Exported name for the type declaration.
80
- * When omitted, returns only the raw type node.
81
- */
82
- name?: string
83
-
84
- /**
85
- * JSDoc comment to attach to the generated type.
86
- */
87
- description?: string
88
- /**
89
- * Properties to exclude using `Omit<Type, Keys>`.
90
- * Forces type alias syntax regardless of `syntaxType` setting.
91
- */
92
- keysToOmit?: Array<string> | null
93
- /**
94
- * Transforms raw schema names into valid TypeScript identifiers.
95
- */
96
- resolver: ResolverTs
97
- /**
98
- * Schema names that represent enums for suffixed key references.
99
- */
100
- enumSchemaNames?: Set<string>
101
- /**
102
- * Custom handler map for node type overrides.
103
- */
104
- nodes?: PrinterTsNodes
105
- }
106
-
107
- /**
108
- * TypeScript printer factory options: maps `SchemaNode` → `ts.TypeNode` (raw) or `ts.Node` (full declaration).
109
- */
110
- export type PrinterTsFactory = ast.PrinterFactoryOptions<'typescript', PrinterTsOptions, ts.TypeNode, string>
111
-
112
- type PrinterTs = PrinterTsFactory
113
-
114
- /**
115
- * TypeScript type printer built with `definePrinter`.
116
- *
117
- * Converts a `SchemaNode` AST node into a TypeScript AST node:
118
- * - **`printer.print(node)`** — when `options.typeName` is set, returns a full
119
- * `type Name = …` or `interface Name { … }` declaration (`ts.Node`).
120
- * Without `typeName`, returns the raw `ts.TypeNode` for the schema.
121
- *
122
- * Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
123
- * over per printer instance, so each call to `printerTs(options)` produces an independent printer.
124
- *
125
- * @example Raw type node (no `typeName`)
126
- * ```ts
127
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
128
- * const typeNode = printer.print(schemaNode) // ts.TypeNode
129
- * ```
130
- *
131
- * @example Full declaration (with `typeName`)
132
- * ```ts
133
- * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
134
- * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
135
- * ```
136
- */
137
- export const printerTs = ast.definePrinter<PrinterTs>((options) => {
138
- const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(options.optionalType)
139
-
140
- return {
141
- name: 'typescript',
142
- options,
143
- nodes: {
144
- any: () => factory.keywordTypeNodes.any,
145
- unknown: () => factory.keywordTypeNodes.unknown,
146
- void: () => factory.keywordTypeNodes.void,
147
- never: () => factory.keywordTypeNodes.never,
148
- boolean: () => factory.keywordTypeNodes.boolean,
149
- null: () => factory.keywordTypeNodes.null,
150
- blob: () => factory.createTypeReferenceNode('Blob', []),
151
- string: () => factory.keywordTypeNodes.string,
152
- uuid: () => factory.keywordTypeNodes.string,
153
- email: () => factory.keywordTypeNodes.string,
154
- url: (node) => {
155
- if (node.path) {
156
- return factory.createUrlTemplateType(node.path)
157
- }
158
- return factory.keywordTypeNodes.string
159
- },
160
- ipv4: () => factory.keywordTypeNodes.string,
161
- ipv6: () => factory.keywordTypeNodes.string,
162
- datetime: () => factory.keywordTypeNodes.string,
163
- number: () => factory.keywordTypeNodes.number,
164
- integer: () => factory.keywordTypeNodes.number,
165
- bigint: () => factory.keywordTypeNodes.bigint,
166
- date: factory.dateOrStringNode,
167
- time: factory.dateOrStringNode,
168
- ref(node) {
169
- if (!node.name) {
170
- return null
171
- }
172
- // Parser-generated refs (with $ref) carry raw schema names that need resolving.
173
- // Use the canonical name from the $ref path — node.name may have been overridden
174
- // (e.g. by single-member allOf flatten using the property-derived child name).
175
- // Inline refs (without $ref) from utils already carry resolved type names.
176
- const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name) : node.name
177
-
178
- // When a Key suffix is configured, enum refs must use the suffixed name (e.g. `StatusKey`)
179
- // so the reference matches what the enum file actually exports.
180
- const isEnumRef =
181
- node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName)
182
-
183
- const name = isEnumRef
184
- ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix!)
185
- : node.ref
186
- ? this.options.resolver.default(refName, 'type')
187
- : refName
188
-
189
- return factory.createTypeReferenceNode(name, undefined)
190
- },
191
- enum(node) {
192
- const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
193
-
194
- if (this.options.enumType === 'inlineLiteral' || !node.name) {
195
- const literalNodes = values
196
- .filter((v): v is string | number | boolean => v !== null && v !== undefined)
197
- .map((value) => factory.constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
198
- .filter(isNonNullable)
199
-
200
- return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
201
- }
202
-
203
- const resolvedName =
204
- ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix
205
- ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix)
206
- : this.options.resolver.default(node.name, 'type')
207
-
208
- return factory.createTypeReferenceNode(resolvedName, undefined)
209
- },
210
- union(node) {
211
- const members = node.members ?? []
212
-
213
- const hasStringLiteral = members.some((m) => {
214
- const enumNode = ast.narrowSchema(m, ast.schemaTypes.enum)
215
- return enumNode?.primitive === 'string'
216
- })
217
- const hasPlainString = members.some((m) => ast.isStringType(m))
218
-
219
- if (hasStringLiteral && hasPlainString) {
220
- const memberNodes = members
221
- .map((m) => {
222
- if (ast.isStringType(m)) {
223
- return factory.createIntersectionDeclaration({
224
- nodes: [factory.keywordTypeNodes.string, factory.createTypeLiteralNode([])],
225
- withParentheses: true,
226
- })
227
- }
228
-
229
- return this.transform(m)
230
- })
231
- .filter(isNonNullable)
232
-
233
- return factory.createUnionDeclaration({ withParentheses: true, nodes: memberNodes }) ?? undefined
234
- }
235
-
236
- return factory.createUnionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(members, this.transform) }) ?? undefined
237
- },
238
- intersection(node) {
239
- return factory.createIntersectionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(node.members, this.transform) }) ?? null
240
- },
241
- array(node) {
242
- const itemNodes = (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable)
243
-
244
- return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? null
245
- },
246
- tuple(node) {
247
- return factory.buildTupleNode(node, this.transform) ?? null
248
- },
249
- object(node) {
250
- const { transform, options } = this
251
-
252
- const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType)
253
-
254
- const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
255
- const baseType = transform(prop.schema) ?? factory.keywordTypeNodes.unknown
256
- const type = factory.buildPropertyType(prop.schema, baseType, options.optionalType)
257
- const propMeta = ast.syncSchemaRef(prop.schema)
258
-
259
- const propertyNode = factory.createPropertySignature({
260
- questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
261
- name: prop.name,
262
- type,
263
- readOnly: propMeta?.readOnly,
264
- })
265
-
266
- return factory.appendJSDocToNode({ node: propertyNode, comments: buildPropertyJSDocComments(prop.schema) })
267
- })
268
-
269
- const allElements = [...propertyNodes, ...factory.buildIndexSignatures(node, propertyNodes.length, transform)]
270
-
271
- if (!allElements.length) {
272
- return factory.keywordTypeNodes.object
273
- }
274
-
275
- return factory.createTypeLiteralNode(allElements)
276
- },
277
- ...options.nodes,
278
- },
279
- print(node) {
280
- const { name, syntaxType = 'type', description, keysToOmit } = this.options
281
-
282
- const transformed = this.transform(node)
283
- if (!transformed) return null
284
-
285
- // For ref nodes, structural metadata lives on node.schema rather than the ref node itself.
286
- const meta = ast.syncSchemaRef(node)
287
-
288
- // Without name, apply modifiers inline and return.
289
- if (!name) {
290
- const withNullable = meta.nullable ? factory.createUnionDeclaration({ nodes: [transformed, factory.keywordTypeNodes.null] }) : transformed
291
- const result =
292
- (meta.nullish || meta.optional) && addsUndefined
293
- ? factory.createUnionDeclaration({ nodes: [withNullable, factory.keywordTypeNodes.undefined] })
294
- : withNullable
295
- return parserTs.print(result)
296
- }
297
-
298
- // When keysToOmit is present, wrap with Omit first, then apply nullable/optional
299
- // modifiers so they are not swallowed by NonNullable inside createOmitDeclaration.
300
- const inner = (() => {
301
- const omitted: ts.TypeNode = keysToOmit?.length
302
- ? factory.createOmitDeclaration({ keys: keysToOmit, type: transformed, nonNullable: true })
303
- : transformed
304
- const withNullable = meta.nullable ? factory.createUnionDeclaration({ nodes: [omitted, factory.keywordTypeNodes.null] }) : omitted
305
- // For named type declarations (type aliases), optional/nullish always produces | undefined
306
- // regardless of optionalType — the questionToken ? modifier only applies to object properties.
307
- return meta.nullish || meta.optional ? factory.createUnionDeclaration({ nodes: [withNullable, factory.keywordTypeNodes.undefined] }) : withNullable
308
- })()
309
-
310
- const useTypeGeneration = syntaxType === 'type' || inner.kind === factory.syntaxKind.union || !!keysToOmit?.length
311
-
312
- const typeNode = factory.createTypeDeclaration({
313
- name,
314
- isExportable: true,
315
- type: inner,
316
- syntax: useTypeGeneration ? 'type' : 'interface',
317
- comments: buildPropertyJSDocComments({
318
- ...meta,
319
- description,
320
- }),
321
- })
322
-
323
- return parserTs.print(typeNode)
324
- },
325
- }
326
- })
@@ -1,69 +0,0 @@
1
- import { ensureValidVarName, pascalCase } from '@internals/utils'
2
- import { defineResolver } from '@kubb/core'
3
- import type { PluginTs } from '../types.ts'
4
-
5
- /**
6
- * Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
7
- * for every generated TypeScript type. Import this in other plugins that need
8
- * to reference the exact names `plugin-ts` produces without duplicating the
9
- * casing/file-layout rules.
10
- *
11
- * The `default` method is supplied by `defineResolver`. It uses PascalCase for
12
- * type names and PascalCase-with-isFile for files.
13
- *
14
- * @example Resolve a type and file name
15
- * ```ts
16
- * import { resolverTs } from '@kubb/plugin-ts'
17
- *
18
- * resolverTs.default('list pets', 'type') // 'ListPets'
19
- * resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
20
- * resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
21
- * ```
22
- */
23
- export const resolverTs = defineResolver<PluginTs>(() => {
24
- return {
25
- name: 'default',
26
- pluginName: 'plugin-ts',
27
- default(name, type) {
28
- const resolved = pascalCase(name, { isFile: type === 'file' })
29
- return type === 'file' ? resolved : ensureValidVarName(resolved)
30
- },
31
- resolveTypeName(name) {
32
- return ensureValidVarName(pascalCase(name))
33
- },
34
- resolvePathName(name, type) {
35
- const resolved = pascalCase(name, { isFile: type === 'file' })
36
- return type === 'file' ? resolved : ensureValidVarName(resolved)
37
- },
38
- resolveParamName(node, param) {
39
- return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`)
40
- },
41
- resolveResponseStatusName(node, statusCode) {
42
- return this.resolveTypeName(`${node.operationId} Status ${statusCode}`)
43
- },
44
- resolveDataName(node) {
45
- return this.resolveTypeName(`${node.operationId} Data`)
46
- },
47
- resolveRequestConfigName(node) {
48
- return this.resolveTypeName(`${node.operationId} RequestConfig`)
49
- },
50
- resolveResponsesName(node) {
51
- return this.resolveTypeName(`${node.operationId} Responses`)
52
- },
53
- resolveResponseName(node) {
54
- return this.resolveTypeName(`${node.operationId} Response`)
55
- },
56
- resolveEnumKeyName(node, enumTypeSuffix = 'key') {
57
- return `${this.resolveTypeName(node.name ?? '')}${enumTypeSuffix}`
58
- },
59
- resolvePathParamsName(node, param) {
60
- return this.resolveParamName(node, param)
61
- },
62
- resolveQueryParamsName(node, param) {
63
- return this.resolveParamName(node, param)
64
- },
65
- resolveHeaderParamsName(node, param) {
66
- return this.resolveParamName(node, param)
67
- },
68
- }
69
- })