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