@kubb/plugin-ts 5.0.0-beta.15 → 5.0.0-beta.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 +79 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +81 -58
- package/dist/index.js +80 -62
- package/dist/index.js.map +1 -1
- package/extension.yaml +690 -247
- package/package.json +5 -5
- package/src/components/Enum.tsx +3 -3
- package/src/factory.ts +8 -13
- package/src/generators/typeGenerator.tsx +22 -13
- package/src/plugin.ts +18 -9
- package/src/printers/printerTs.ts +24 -27
- package/src/resolvers/resolverTs.ts +11 -10
- package/src/types.ts +44 -37
- package/src/utils.ts +9 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-ts",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.25",
|
|
4
4
|
"description": "Generate TypeScript types, interfaces, and enums from your OpenAPI specification. The foundational plugin that powers type safety across the entire Kubb ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-generation",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"registry": "https://registry.npmjs.org/"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@kubb/core": "5.0.0-beta.
|
|
50
|
-
"@kubb/parser-ts": "5.0.0-beta.
|
|
51
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
49
|
+
"@kubb/core": "5.0.0-beta.25",
|
|
50
|
+
"@kubb/parser-ts": "5.0.0-beta.25",
|
|
51
|
+
"@kubb/renderer-jsx": "5.0.0-beta.25",
|
|
52
52
|
"remeda": "^2.34.1",
|
|
53
53
|
"typescript": "^6.0.3"
|
|
54
54
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@internals/utils": "0.0.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
60
|
+
"@kubb/renderer-jsx": "5.0.0-beta.25"
|
|
61
61
|
},
|
|
62
62
|
"size-limit": [
|
|
63
63
|
{
|
package/src/components/Enum.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { camelCase, trimQuotes } from '@internals/utils'
|
|
2
2
|
import type { ast } from '@kubb/core'
|
|
3
|
-
import {
|
|
3
|
+
import { parserTs } from '@kubb/parser-ts'
|
|
4
4
|
import { File } from '@kubb/renderer-jsx'
|
|
5
5
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
6
6
|
import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_WITH_TYPE_ONLY } from '../constants.ts'
|
|
@@ -72,11 +72,11 @@ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }
|
|
|
72
72
|
<>
|
|
73
73
|
{nameNode && (
|
|
74
74
|
<File.Source name={enumName} isExportable isIndexable isTypeOnly={false}>
|
|
75
|
-
{
|
|
75
|
+
{parserTs.print(nameNode)}
|
|
76
76
|
</File.Source>
|
|
77
77
|
)}
|
|
78
78
|
<File.Source name={typeName} isIndexable isExportable={ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType)} isTypeOnly={ENUM_TYPES_WITH_TYPE_ONLY.has(enumType)}>
|
|
79
|
-
{
|
|
79
|
+
{parserTs.print(typeNode)}
|
|
80
80
|
</File.Source>
|
|
81
81
|
</>
|
|
82
82
|
)
|
package/src/factory.ts
CHANGED
|
@@ -198,7 +198,7 @@ export function createParameterSignature(
|
|
|
198
198
|
* Creates a JSDoc comment node from an array of comment strings.
|
|
199
199
|
* Returns null if no comments are provided.
|
|
200
200
|
*/
|
|
201
|
-
export function createJSDoc({ comments }: { comments: string
|
|
201
|
+
export function createJSDoc({ comments }: { comments: Array<string> }) {
|
|
202
202
|
if (!comments.length) {
|
|
203
203
|
return null
|
|
204
204
|
}
|
|
@@ -338,7 +338,7 @@ export function createTypeDeclaration({
|
|
|
338
338
|
/**
|
|
339
339
|
* Creates a TypeScript namespace declaration (exported module).
|
|
340
340
|
*/
|
|
341
|
-
export function createNamespaceDeclaration({ statements, name }: { name: string; statements: ts.Statement
|
|
341
|
+
export function createNamespaceDeclaration({ statements, name }: { name: string; statements: Array<ts.Statement> }) {
|
|
342
342
|
return factory.createModuleDeclaration(
|
|
343
343
|
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
344
344
|
factory.createIdentifier(name),
|
|
@@ -372,13 +372,8 @@ export function createImportDeclaration({
|
|
|
372
372
|
isNameSpace?: boolean
|
|
373
373
|
}) {
|
|
374
374
|
if (!Array.isArray(name)) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (isNameSpace) {
|
|
379
|
-
importPropertyName = undefined
|
|
380
|
-
importName = factory.createNamespaceImport(factory.createIdentifier(name))
|
|
381
|
-
}
|
|
375
|
+
const importPropertyName = isNameSpace ? undefined : factory.createIdentifier(name)
|
|
376
|
+
const importName = isNameSpace ? factory.createNamespaceImport(factory.createIdentifier(name)) : undefined
|
|
382
377
|
|
|
383
378
|
return factory.createImportDeclaration(
|
|
384
379
|
undefined,
|
|
@@ -524,7 +519,7 @@ export function createEnumDeclaration({
|
|
|
524
519
|
* Enum name in PascalCase.
|
|
525
520
|
*/
|
|
526
521
|
typeName: string
|
|
527
|
-
enums: [key: string | number, value: string | number | boolean]
|
|
522
|
+
enums: Array<[key: string | number, value: string | number | boolean]>
|
|
528
523
|
/**
|
|
529
524
|
* Choose the casing for enum key names.
|
|
530
525
|
* @default 'none'
|
|
@@ -745,8 +740,8 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
|
|
|
745
740
|
}
|
|
746
741
|
|
|
747
742
|
const segments = normalized.split(/(\{[^}]+\})/)
|
|
748
|
-
const parts: string
|
|
749
|
-
const parameterIndices: number
|
|
743
|
+
const parts: Array<string> = []
|
|
744
|
+
const parameterIndices: Array<number> = []
|
|
750
745
|
|
|
751
746
|
segments.forEach((segment) => {
|
|
752
747
|
if (segment.startsWith('{') && segment.endsWith('}')) {
|
|
@@ -758,7 +753,7 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
|
|
|
758
753
|
})
|
|
759
754
|
|
|
760
755
|
const head = ts.factory.createTemplateHead(parts[0] || '')
|
|
761
|
-
const templateSpans: ts.TemplateLiteralTypeSpan
|
|
756
|
+
const templateSpans: Array<ts.TemplateLiteralTypeSpan> = []
|
|
762
757
|
|
|
763
758
|
parameterIndices.forEach((paramIndex, i) => {
|
|
764
759
|
const isLast = i === parameterIndices.length - 1
|
|
@@ -24,12 +24,18 @@ function getPerContentTypeName(dataName: string, suffix: string): string {
|
|
|
24
24
|
return dataName + suffix
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
|
|
29
|
+
* schema in the spec plus per-operation request, response, and parameter
|
|
30
|
+
* types. Drop-replace with a custom `Generator<PluginTs>` to change how
|
|
31
|
+
* TypeScript output is produced.
|
|
32
|
+
*/
|
|
27
33
|
export const typeGenerator = defineGenerator<PluginTs>({
|
|
28
34
|
name: 'typescript',
|
|
29
35
|
renderer: jsxRendererSync,
|
|
30
36
|
schema(node, ctx) {
|
|
31
37
|
const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options
|
|
32
|
-
const { adapter, config, resolver, root
|
|
38
|
+
const { adapter, config, resolver, root } = ctx
|
|
33
39
|
|
|
34
40
|
if (!node.name) {
|
|
35
41
|
return
|
|
@@ -37,7 +43,7 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
37
43
|
const mode = ctx.getMode(output)
|
|
38
44
|
// Build a set of schema names that are enums so the ref handler and getImports
|
|
39
45
|
// callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
|
|
40
|
-
const enumSchemaNames = new Set(
|
|
46
|
+
const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
|
|
41
47
|
|
|
42
48
|
function resolveImportName(schemaName: string): string {
|
|
43
49
|
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
|
|
@@ -48,14 +54,14 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
48
54
|
|
|
49
55
|
const imports = adapter.getImports(node, (schemaName) => ({
|
|
50
56
|
name: resolveImportName(schemaName),
|
|
51
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
57
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
|
|
52
58
|
}))
|
|
53
59
|
|
|
54
60
|
const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum)
|
|
55
61
|
|
|
56
62
|
const meta = {
|
|
57
63
|
name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveTypeName(node.name),
|
|
58
|
-
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
|
|
64
|
+
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
|
|
59
65
|
} as const
|
|
60
66
|
|
|
61
67
|
const schemaPrinter = printerTs({
|
|
@@ -76,8 +82,8 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
76
82
|
baseName={meta.file.baseName}
|
|
77
83
|
path={meta.file.path}
|
|
78
84
|
meta={meta.file.meta}
|
|
79
|
-
banner={resolver.resolveBanner(
|
|
80
|
-
footer={resolver.resolveFooter(
|
|
85
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config })}
|
|
86
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config })}
|
|
81
87
|
>
|
|
82
88
|
{mode === 'split' &&
|
|
83
89
|
imports.map((imp) => (
|
|
@@ -97,19 +103,22 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
97
103
|
},
|
|
98
104
|
operation(node, ctx) {
|
|
99
105
|
const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options
|
|
100
|
-
const { adapter, config, resolver, root
|
|
106
|
+
const { adapter, config, resolver, root } = ctx
|
|
101
107
|
|
|
102
108
|
const mode = ctx.getMode(output)
|
|
103
109
|
|
|
104
110
|
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
105
111
|
|
|
106
112
|
const meta = {
|
|
107
|
-
file: resolver.resolveFile(
|
|
113
|
+
file: resolver.resolveFile(
|
|
114
|
+
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
115
|
+
{ root, output, group: group ?? undefined },
|
|
116
|
+
),
|
|
108
117
|
} as const
|
|
109
118
|
|
|
110
119
|
// Build a set of schema names that are enums so the ref handler and getImports
|
|
111
120
|
// callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
|
|
112
|
-
const enumSchemaNames = new Set(
|
|
121
|
+
const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
|
|
113
122
|
|
|
114
123
|
function resolveImportName(schemaName: string): string {
|
|
115
124
|
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
|
|
@@ -118,12 +127,12 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
118
127
|
return resolver.resolveTypeName(schemaName)
|
|
119
128
|
}
|
|
120
129
|
|
|
121
|
-
function renderSchemaType({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
|
|
130
|
+
function renderSchemaType({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> | null }) {
|
|
122
131
|
if (!schema) return null
|
|
123
132
|
|
|
124
133
|
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
125
134
|
name: resolveImportName(schemaName),
|
|
126
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
135
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
|
|
127
136
|
}))
|
|
128
137
|
|
|
129
138
|
const schemaPrinter = printerTs({
|
|
@@ -281,8 +290,8 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
281
290
|
baseName={meta.file.baseName}
|
|
282
291
|
path={meta.file.path}
|
|
283
292
|
meta={meta.file.meta}
|
|
284
|
-
banner={resolver.resolveBanner(
|
|
285
|
-
footer={resolver.resolveFooter(
|
|
293
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config })}
|
|
294
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config })}
|
|
286
295
|
>
|
|
287
296
|
{paramTypes}
|
|
288
297
|
{responseTypes}
|
package/src/plugin.ts
CHANGED
|
@@ -5,23 +5,32 @@ import { resolverTs } from './resolvers/resolverTs.ts'
|
|
|
5
5
|
import type { PluginTs } from './types.ts'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Canonical plugin name for `@kubb/plugin-ts
|
|
8
|
+
* Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
|
|
9
|
+
* cross-plugin dependency references.
|
|
9
10
|
*/
|
|
10
11
|
export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* and writes barrel files based on `output.barrelType`.
|
|
14
|
+
* Generates TypeScript `type` aliases and `interface` declarations from an
|
|
15
|
+
* OpenAPI spec. The foundation that every other Kubb plugin builds on:
|
|
16
|
+
* clients, query hooks, mocks, and validators all reference the names this
|
|
17
|
+
* plugin produces.
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* ```ts
|
|
21
|
-
* import
|
|
21
|
+
* import { defineConfig } from 'kubb'
|
|
22
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
22
23
|
*
|
|
23
24
|
* export default defineConfig({
|
|
24
|
-
*
|
|
25
|
+
* input: { path: './petStore.yaml' },
|
|
26
|
+
* output: { path: './src/gen' },
|
|
27
|
+
* plugins: [
|
|
28
|
+
* pluginTs({
|
|
29
|
+
* output: { path: './types' },
|
|
30
|
+
* enumType: 'asConst',
|
|
31
|
+
* optionalType: 'questionTokenAndUndefined',
|
|
32
|
+
* }),
|
|
33
|
+
* ],
|
|
25
34
|
* })
|
|
26
35
|
* ```
|
|
27
36
|
*/
|
|
@@ -55,7 +64,7 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
|
|
|
55
64
|
return `${camelCase(ctx.group)}Controller`
|
|
56
65
|
},
|
|
57
66
|
} satisfies Group)
|
|
58
|
-
:
|
|
67
|
+
: null
|
|
59
68
|
|
|
60
69
|
return {
|
|
61
70
|
name: pluginTsName,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ast } from '@kubb/core'
|
|
2
|
-
import {
|
|
2
|
+
import { parserTs } from '@kubb/parser-ts'
|
|
3
3
|
import type ts from 'typescript'
|
|
4
4
|
import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from '../constants.ts'
|
|
5
5
|
import * as factory from '../factory.ts'
|
|
@@ -89,7 +89,7 @@ export type PrinterTsOptions = {
|
|
|
89
89
|
* Properties to exclude using `Omit<Type, Keys>`.
|
|
90
90
|
* Forces type alias syntax regardless of `syntaxType` setting.
|
|
91
91
|
*/
|
|
92
|
-
keysToOmit?: Array<string>
|
|
92
|
+
keysToOmit?: Array<string> | null
|
|
93
93
|
/**
|
|
94
94
|
* Transforms raw schema names into valid TypeScript identifiers.
|
|
95
95
|
*/
|
|
@@ -167,7 +167,7 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
|
|
|
167
167
|
time: factory.dateOrStringNode,
|
|
168
168
|
ref(node) {
|
|
169
169
|
if (!node.name) {
|
|
170
|
-
return
|
|
170
|
+
return null
|
|
171
171
|
}
|
|
172
172
|
// Parser-generated refs (with $ref) carry raw schema names that need resolving.
|
|
173
173
|
// Use the canonical name from the $ref path — node.name may have been overridden
|
|
@@ -236,15 +236,15 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
|
|
|
236
236
|
return factory.createUnionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(members, this.transform) }) ?? undefined
|
|
237
237
|
},
|
|
238
238
|
intersection(node) {
|
|
239
|
-
return factory.createIntersectionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(node.members, this.transform) }) ??
|
|
239
|
+
return factory.createIntersectionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(node.members, this.transform) }) ?? null
|
|
240
240
|
},
|
|
241
241
|
array(node) {
|
|
242
242
|
const itemNodes = (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable)
|
|
243
243
|
|
|
244
|
-
return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ??
|
|
244
|
+
return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? null
|
|
245
245
|
},
|
|
246
246
|
tuple(node) {
|
|
247
|
-
return factory.buildTupleNode(node, this.transform)
|
|
247
|
+
return factory.buildTupleNode(node, this.transform) ?? null
|
|
248
248
|
},
|
|
249
249
|
object(node) {
|
|
250
250
|
const { transform, options } = this
|
|
@@ -279,36 +279,33 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
|
|
|
279
279
|
print(node) {
|
|
280
280
|
const { name, syntaxType = 'type', description, keysToOmit } = this.options
|
|
281
281
|
|
|
282
|
-
|
|
283
|
-
if (!
|
|
282
|
+
const transformed = this.transform(node)
|
|
283
|
+
if (!transformed) return null
|
|
284
284
|
|
|
285
285
|
// For ref nodes, structural metadata lives on node.schema rather than the ref node itself.
|
|
286
286
|
const meta = ast.syncSchemaRef(node)
|
|
287
287
|
|
|
288
288
|
// Without name, apply modifiers inline and return.
|
|
289
289
|
if (!name) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return safePrint(base)
|
|
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)
|
|
297
296
|
}
|
|
298
297
|
|
|
299
298
|
// When keysToOmit is present, wrap with Omit first, then apply nullable/optional
|
|
300
299
|
// modifiers so they are not swallowed by NonNullable inside createOmitDeclaration.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.undefined] })
|
|
311
|
-
}
|
|
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
|
+
})()
|
|
312
309
|
|
|
313
310
|
const useTypeGeneration = syntaxType === 'type' || inner.kind === factory.syntaxKind.union || !!keysToOmit?.length
|
|
314
311
|
|
|
@@ -323,7 +320,7 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
|
|
|
323
320
|
}),
|
|
324
321
|
})
|
|
325
322
|
|
|
326
|
-
return
|
|
323
|
+
return parserTs.print(typeNode)
|
|
327
324
|
},
|
|
328
325
|
}
|
|
329
326
|
})
|
|
@@ -3,20 +3,21 @@ import { defineResolver } from '@kubb/core'
|
|
|
3
3
|
import type { PluginTs } from '../types.ts'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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.
|
|
9
10
|
*
|
|
10
|
-
* The `default` method is
|
|
11
|
-
*
|
|
11
|
+
* The `default` method is supplied by `defineResolver`. It uses PascalCase for
|
|
12
|
+
* type names and PascalCase-with-isFile for files.
|
|
12
13
|
*
|
|
13
|
-
* @example
|
|
14
|
+
* @example Resolve a type and file name
|
|
14
15
|
* ```ts
|
|
15
|
-
* import {
|
|
16
|
+
* import { resolverTs } from '@kubb/plugin-ts'
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* resolverTs.default('list pets', 'type') // 'ListPets'
|
|
19
|
+
* resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
|
|
20
|
+
* resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
22
23
|
export const resolverTs = defineResolver<PluginTs>(() => {
|
package/src/types.ts
CHANGED
|
@@ -115,7 +115,7 @@ type EnumTypeOptions =
|
|
|
115
115
|
/**
|
|
116
116
|
* Suffix appended to the generated type alias name.
|
|
117
117
|
*
|
|
118
|
-
* Only affects the type alias
|
|
118
|
+
* Only affects the type alias; the const object name is unchanged.
|
|
119
119
|
*
|
|
120
120
|
* @default 'Key'
|
|
121
121
|
* @example enumTypeSuffix: 'Value' → `export type PetStatusValue = …`
|
|
@@ -172,81 +172,89 @@ type EnumTypeOptions =
|
|
|
172
172
|
enumTypeSuffix?: never
|
|
173
173
|
/**
|
|
174
174
|
* `enumKeyCasing` has no effect for this `enumType`.
|
|
175
|
-
* Literal and inlineLiteral modes emit only values
|
|
175
|
+
* Literal and inlineLiteral modes emit only values; keys are discarded entirely.
|
|
176
176
|
*/
|
|
177
177
|
enumKeyCasing?: never
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
export type Options = {
|
|
181
181
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
182
|
+
* Where the generated `.ts` files are written and how they are exported.
|
|
183
|
+
*
|
|
184
|
+
* @default { path: 'types', barrel: { type: 'named' } }
|
|
184
185
|
*/
|
|
185
186
|
output?: Output
|
|
186
187
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
188
|
+
* Media type read from the OpenAPI spec when an operation defines several.
|
|
189
|
+
* Defaults to the first JSON-compatible media type Kubb finds.
|
|
189
190
|
*/
|
|
190
191
|
contentType?: 'application/json' | (string & {})
|
|
191
192
|
/**
|
|
192
|
-
*
|
|
193
|
+
* Split generated files into subfolders based on the operation's tag.
|
|
193
194
|
*/
|
|
194
195
|
group?: Group
|
|
195
196
|
/**
|
|
196
|
-
*
|
|
197
|
+
* Skip operations matching at least one entry in the list.
|
|
197
198
|
*/
|
|
198
199
|
exclude?: Array<Exclude>
|
|
199
200
|
/**
|
|
200
|
-
*
|
|
201
|
+
* Restrict generation to operations matching at least one entry in the list.
|
|
201
202
|
*/
|
|
202
203
|
include?: Array<Include>
|
|
203
204
|
/**
|
|
204
|
-
*
|
|
205
|
+
* Apply a different options object to operations matching a pattern.
|
|
205
206
|
*/
|
|
206
207
|
override?: Array<Override<ResolvedOptions>>
|
|
207
208
|
/**
|
|
208
|
-
*
|
|
209
|
-
* - 'type'
|
|
210
|
-
* - 'interface'
|
|
209
|
+
* Whether object schemas are emitted as `type` aliases or `interface` declarations.
|
|
210
|
+
* - `'type'` emits closed type aliases. Safer default for generated code.
|
|
211
|
+
* - `'interface'` emits interfaces. Useful when consumers rely on declaration merging.
|
|
212
|
+
*
|
|
211
213
|
* @default 'type'
|
|
214
|
+
* @see https://www.totaltypescript.com/type-vs-interface-which-should-you-use
|
|
212
215
|
*/
|
|
213
216
|
syntaxType?: 'type' | 'interface'
|
|
214
217
|
/**
|
|
215
|
-
*
|
|
216
|
-
* - 'questionToken'
|
|
217
|
-
* - 'undefined'
|
|
218
|
-
* - 'questionTokenAndUndefined'
|
|
218
|
+
* How optional properties are written in generated types.
|
|
219
|
+
* - `'questionToken'` — `type?: string`. The property may be missing.
|
|
220
|
+
* - `'undefined'` — `type: string | undefined`. Required to exist, may be `undefined`.
|
|
221
|
+
* - `'questionTokenAndUndefined'` — `type?: string | undefined`. Strictest.
|
|
222
|
+
*
|
|
219
223
|
* @default 'questionToken'
|
|
224
|
+
* @note Pick `'questionTokenAndUndefined'` when `exactOptionalPropertyTypes` is on in `tsconfig.json`.
|
|
220
225
|
*/
|
|
221
226
|
optionalType?: 'questionToken' | 'undefined' | 'questionTokenAndUndefined'
|
|
222
227
|
/**
|
|
223
|
-
*
|
|
224
|
-
* - '
|
|
225
|
-
* - '
|
|
228
|
+
* Syntax used for array types.
|
|
229
|
+
* - `'array'` — `Type[]`. Shorter.
|
|
230
|
+
* - `'generic'` — `Array<Type>`. More readable for complex element types.
|
|
231
|
+
*
|
|
226
232
|
* @default 'array'
|
|
227
233
|
*/
|
|
228
234
|
arrayType?: 'generic' | 'array'
|
|
229
235
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* @note
|
|
236
|
+
* Rename properties inside `PathParams`, `QueryParams`, and `HeaderParams` types.
|
|
237
|
+
* Response and request body types are not affected.
|
|
238
|
+
*
|
|
239
|
+
* @note Every plugin that touches operation parameters must use the same value.
|
|
234
240
|
*/
|
|
235
241
|
paramsCasing?: 'camelcase'
|
|
236
242
|
/**
|
|
237
|
-
*
|
|
243
|
+
* Custom generators that run alongside the built-in TypeScript generators.
|
|
238
244
|
*/
|
|
239
245
|
generators?: Array<Generator<PluginTs>>
|
|
240
246
|
/**
|
|
241
|
-
* Override
|
|
242
|
-
*
|
|
247
|
+
* Override how names and file paths are built for generated symbols.
|
|
248
|
+
* Methods you omit fall back to the default `resolverTs`. `this` is bound to the
|
|
249
|
+
* full resolver, so `this.default(name, 'function')` delegates to the built-in
|
|
250
|
+
* implementation.
|
|
243
251
|
*/
|
|
244
252
|
resolver?: Partial<ResolverTs> & ThisType<ResolverTs>
|
|
245
253
|
/**
|
|
246
|
-
* AST visitor applied to each schema
|
|
247
|
-
*
|
|
254
|
+
* AST visitor applied to each schema or operation node before printing.
|
|
255
|
+
* Methods you omit fall back to the preset transformer.
|
|
248
256
|
*
|
|
249
|
-
* @example
|
|
257
|
+
* @example Drop writeOnly properties from response types
|
|
250
258
|
* ```ts
|
|
251
259
|
* transformer: {
|
|
252
260
|
* property(node) {
|
|
@@ -257,18 +265,17 @@ export type Options = {
|
|
|
257
265
|
*/
|
|
258
266
|
transformer?: ast.Visitor
|
|
259
267
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* built-in handler for that type. Use `this.transform` to recurse into nested schema nodes.
|
|
268
|
+
* Replace the TypeScript handler for a specific schema type (`'integer'`, `'date'`, ...).
|
|
269
|
+
* Each handler returns a TypeScript AST node for that schema type. Use `this.transform`
|
|
270
|
+
* to recurse into nested schema nodes.
|
|
264
271
|
*
|
|
265
|
-
* @example
|
|
272
|
+
* @example Use the JavaScript `Date` object for date schemas
|
|
266
273
|
* ```ts
|
|
267
274
|
* import ts from 'typescript'
|
|
268
275
|
* pluginTs({
|
|
269
276
|
* printer: {
|
|
270
277
|
* nodes: {
|
|
271
|
-
* date(
|
|
278
|
+
* date() {
|
|
272
279
|
* return ts.factory.createTypeReferenceNode('Date', [])
|
|
273
280
|
* },
|
|
274
281
|
* },
|
|
@@ -286,7 +293,7 @@ type ResolvedOptions = {
|
|
|
286
293
|
exclude: Array<Exclude>
|
|
287
294
|
include: Array<Include> | undefined
|
|
288
295
|
override: Array<Override<ResolvedOptions>>
|
|
289
|
-
group: Group |
|
|
296
|
+
group: Group | null
|
|
290
297
|
enumType: NonNullable<Options['enumType']>
|
|
291
298
|
enumTypeSuffix: NonNullable<Options['enumTypeSuffix']>
|
|
292
299
|
enumKeyCasing: EnumKeyCasing
|
package/src/utils.ts
CHANGED
|
@@ -16,19 +16,19 @@ export function buildPropertyJSDocComments(schema: ast.SchemaNode): Array<string
|
|
|
16
16
|
const isArray = meta?.primitive === 'array'
|
|
17
17
|
|
|
18
18
|
return [
|
|
19
|
-
meta && 'description' in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` :
|
|
20
|
-
meta && 'deprecated' in meta && meta.deprecated ? '@deprecated' :
|
|
19
|
+
meta && 'description' in meta && meta.description ? `@description ${jsStringEscape(meta.description)}` : null,
|
|
20
|
+
meta && 'deprecated' in meta && meta.deprecated ? '@deprecated' : null,
|
|
21
21
|
// minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
|
|
22
|
-
!isArray && meta && 'min' in meta && meta.min !== undefined ? `@minLength ${meta.min}` :
|
|
23
|
-
!isArray && meta && 'max' in meta && meta.max !== undefined ? `@maxLength ${meta.max}` :
|
|
24
|
-
meta && 'pattern' in meta && meta.pattern ? `@pattern ${meta.pattern}` :
|
|
22
|
+
!isArray && meta && 'min' in meta && meta.min !== undefined ? `@minLength ${meta.min}` : null,
|
|
23
|
+
!isArray && meta && 'max' in meta && meta.max !== undefined ? `@maxLength ${meta.max}` : null,
|
|
24
|
+
meta && 'pattern' in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
|
|
25
25
|
meta && 'default' in meta && meta.default !== undefined
|
|
26
26
|
? `@default ${'primitive' in meta && meta.primitive === 'string' ? stringify(meta.default as string) : meta.default}`
|
|
27
|
-
:
|
|
28
|
-
meta && 'example' in meta && meta.example !== undefined ? `@example ${meta.example}` :
|
|
27
|
+
: null,
|
|
28
|
+
meta && 'example' in meta && meta.example !== undefined ? `@example ${meta.example}` : null,
|
|
29
29
|
meta && 'primitive' in meta && meta.primitive
|
|
30
|
-
? [`@type ${meta.primitive}`, 'optional' in schema && schema.optional ? ' | undefined' :
|
|
31
|
-
:
|
|
30
|
+
? [`@type ${meta.primitive}`, 'optional' in schema && schema.optional ? ' | undefined' : null].filter(Boolean).join('')
|
|
31
|
+
: null,
|
|
32
32
|
].filter(Boolean)
|
|
33
33
|
}
|
|
34
34
|
|