@kubb/plugin-ts 5.0.0-beta.10 → 5.0.0-beta.100
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/README.md +22 -26
- package/dist/index.cjs +1444 -594
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +641 -271
- package/dist/index.js +1417 -584
- package/dist/index.js.map +1 -1
- package/dist/rolldown-runtime-CNktS9qV.js +17 -0
- package/package.json +8 -22
- package/dist/chunk--u3MIqq1.js +0 -8
- package/extension.yaml +0 -632
- package/src/components/Enum.tsx +0 -83
- package/src/components/Type.tsx +0 -60
- package/src/constants.ts +0 -39
- package/src/factory.ts +0 -982
- package/src/generators/typeGenerator.tsx +0 -296
- package/src/index.ts +0 -9
- package/src/plugin.ts +0 -93
- package/src/printers/functionPrinter.ts +0 -197
- package/src/printers/printerTs.ts +0 -329
- package/src/resolvers/resolverTs.ts +0 -66
- package/src/types.ts +0 -308
- package/src/utils.ts +0 -129
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
import { ast, defineGenerator } from '@kubb/core'
|
|
2
|
-
import { File, jsxRenderer } from '@kubb/renderer-jsx'
|
|
3
|
-
import { Type } from '../components/Type.tsx'
|
|
4
|
-
import { ENUM_TYPES_WITH_KEY_SUFFIX } from '../constants.ts'
|
|
5
|
-
import { printerTs } from '../printers/printerTs.ts'
|
|
6
|
-
import type { PluginTs } from '../types'
|
|
7
|
-
import { buildData, buildResponses, buildResponseUnion } from '../utils.ts'
|
|
8
|
-
|
|
9
|
-
function getContentTypeSuffix(contentType: string): string {
|
|
10
|
-
const baseType = contentType.split(';')[0]!.trim()
|
|
11
|
-
if (baseType === 'application/json') return 'Json'
|
|
12
|
-
if (baseType === 'multipart/form-data') return 'FormData'
|
|
13
|
-
if (baseType === 'application/x-www-form-urlencoded') return 'FormUrlEncoded'
|
|
14
|
-
const subtype = baseType.split('/').pop() ?? baseType
|
|
15
|
-
const parts = subtype.split(/[^a-zA-Z0-9]+/).filter(Boolean)
|
|
16
|
-
if (parts.length === 0) return 'Unknown'
|
|
17
|
-
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function getPerContentTypeName(dataName: string, suffix: string): string {
|
|
21
|
-
if (dataName.endsWith('Data')) {
|
|
22
|
-
return suffix.endsWith('Data') ? dataName.slice(0, -4) + suffix : `${dataName.slice(0, -4)}${suffix}Data`
|
|
23
|
-
}
|
|
24
|
-
return dataName + suffix
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const typeGenerator = defineGenerator<PluginTs>({
|
|
28
|
-
name: 'typescript',
|
|
29
|
-
renderer: jsxRenderer,
|
|
30
|
-
schema(node, ctx) {
|
|
31
|
-
const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options
|
|
32
|
-
const { adapter, config, resolver, root } = ctx
|
|
33
|
-
|
|
34
|
-
if (!node.name) {
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
const mode = ctx.getMode(output)
|
|
38
|
-
// Build a set of schema names that are enums so the ref handler and getImports
|
|
39
|
-
// callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
|
|
40
|
-
const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name!))
|
|
41
|
-
|
|
42
|
-
function resolveImportName(schemaName: string): string {
|
|
43
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
|
|
44
|
-
return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix)
|
|
45
|
-
}
|
|
46
|
-
return resolver.resolveTypeName(schemaName)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const imports = adapter.getImports(node, (schemaName) => ({
|
|
50
|
-
name: resolveImportName(schemaName),
|
|
51
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
52
|
-
}))
|
|
53
|
-
|
|
54
|
-
const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum)
|
|
55
|
-
|
|
56
|
-
const meta = {
|
|
57
|
-
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 }),
|
|
59
|
-
} as const
|
|
60
|
-
|
|
61
|
-
const schemaPrinter = printerTs({
|
|
62
|
-
optionalType,
|
|
63
|
-
arrayType,
|
|
64
|
-
enumType,
|
|
65
|
-
enumTypeSuffix,
|
|
66
|
-
name: meta.name,
|
|
67
|
-
syntaxType,
|
|
68
|
-
description: node.description,
|
|
69
|
-
resolver,
|
|
70
|
-
enumSchemaNames,
|
|
71
|
-
nodes: printer?.nodes,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<File
|
|
76
|
-
baseName={meta.file.baseName}
|
|
77
|
-
path={meta.file.path}
|
|
78
|
-
meta={meta.file.meta}
|
|
79
|
-
banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
|
|
80
|
-
footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
|
|
81
|
-
>
|
|
82
|
-
{mode === 'split' &&
|
|
83
|
-
imports.map((imp) => (
|
|
84
|
-
<File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
|
|
85
|
-
))}
|
|
86
|
-
<Type
|
|
87
|
-
name={meta.name}
|
|
88
|
-
node={node}
|
|
89
|
-
enumType={enumType}
|
|
90
|
-
enumTypeSuffix={enumTypeSuffix}
|
|
91
|
-
enumKeyCasing={enumKeyCasing}
|
|
92
|
-
resolver={resolver}
|
|
93
|
-
printer={schemaPrinter}
|
|
94
|
-
/>
|
|
95
|
-
</File>
|
|
96
|
-
)
|
|
97
|
-
},
|
|
98
|
-
operation(node, ctx) {
|
|
99
|
-
const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options
|
|
100
|
-
const { adapter, config, resolver, root } = ctx
|
|
101
|
-
|
|
102
|
-
const mode = ctx.getMode(output)
|
|
103
|
-
|
|
104
|
-
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
105
|
-
|
|
106
|
-
const meta = {
|
|
107
|
-
file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
|
|
108
|
-
} as const
|
|
109
|
-
|
|
110
|
-
// Build a set of schema names that are enums so the ref handler and getImports
|
|
111
|
-
// callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
|
|
112
|
-
const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name!))
|
|
113
|
-
|
|
114
|
-
function resolveImportName(schemaName: string): string {
|
|
115
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
|
|
116
|
-
return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix)
|
|
117
|
-
}
|
|
118
|
-
return resolver.resolveTypeName(schemaName)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function renderSchemaType({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
|
|
122
|
-
if (!schema) return null
|
|
123
|
-
|
|
124
|
-
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
125
|
-
name: resolveImportName(schemaName),
|
|
126
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
127
|
-
}))
|
|
128
|
-
|
|
129
|
-
const schemaPrinter = printerTs({
|
|
130
|
-
optionalType,
|
|
131
|
-
arrayType,
|
|
132
|
-
enumType,
|
|
133
|
-
enumTypeSuffix,
|
|
134
|
-
name,
|
|
135
|
-
syntaxType,
|
|
136
|
-
description: schema.description,
|
|
137
|
-
keysToOmit,
|
|
138
|
-
resolver,
|
|
139
|
-
enumSchemaNames,
|
|
140
|
-
nodes: printer?.nodes,
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<>
|
|
145
|
-
{mode === 'split' &&
|
|
146
|
-
imports.map((imp) => (
|
|
147
|
-
<File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
|
|
148
|
-
))}
|
|
149
|
-
<Type
|
|
150
|
-
name={name}
|
|
151
|
-
node={schema}
|
|
152
|
-
enumType={enumType}
|
|
153
|
-
enumTypeSuffix={enumTypeSuffix}
|
|
154
|
-
enumKeyCasing={enumKeyCasing}
|
|
155
|
-
resolver={resolver}
|
|
156
|
-
printer={schemaPrinter}
|
|
157
|
-
/>
|
|
158
|
-
</>
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const paramTypes = params.map((param) =>
|
|
163
|
-
renderSchemaType({
|
|
164
|
-
schema: param.schema,
|
|
165
|
-
name: resolver.resolveParamName(node, param),
|
|
166
|
-
}),
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
const requestBodyContent = node.requestBody?.content ?? []
|
|
170
|
-
|
|
171
|
-
function buildRequestType() {
|
|
172
|
-
if (requestBodyContent.length === 0) return null
|
|
173
|
-
if (requestBodyContent.length === 1) {
|
|
174
|
-
const entry = requestBodyContent[0]!
|
|
175
|
-
if (!entry.schema) return null
|
|
176
|
-
return renderSchemaType({
|
|
177
|
-
schema: {
|
|
178
|
-
...entry.schema,
|
|
179
|
-
description: node.requestBody!.description ?? entry.schema.description,
|
|
180
|
-
},
|
|
181
|
-
name: resolver.resolveDataName(node),
|
|
182
|
-
keysToOmit: entry.keysToOmit,
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
// Multiple content types — generate individual types + union alias
|
|
186
|
-
const dataName = resolver.resolveDataName(node)
|
|
187
|
-
const usedNames = new Set<string>()
|
|
188
|
-
const individualItems = requestBodyContent
|
|
189
|
-
.filter((entry) => entry.schema)
|
|
190
|
-
.map((entry) => {
|
|
191
|
-
const baseSuffix = getContentTypeSuffix(entry.contentType)
|
|
192
|
-
let individualName = getPerContentTypeName(dataName, baseSuffix)
|
|
193
|
-
let counter = 2
|
|
194
|
-
while (usedNames.has(individualName)) {
|
|
195
|
-
individualName = getPerContentTypeName(dataName, `${baseSuffix}${counter++}`)
|
|
196
|
-
}
|
|
197
|
-
usedNames.add(individualName)
|
|
198
|
-
return {
|
|
199
|
-
name: individualName,
|
|
200
|
-
rendered: renderSchemaType({
|
|
201
|
-
schema: {
|
|
202
|
-
...entry.schema!,
|
|
203
|
-
description: node.requestBody!.description ?? entry.schema!.description,
|
|
204
|
-
},
|
|
205
|
-
name: individualName,
|
|
206
|
-
keysToOmit: entry.keysToOmit,
|
|
207
|
-
}),
|
|
208
|
-
}
|
|
209
|
-
})
|
|
210
|
-
const unionSchema = ast.createSchema({
|
|
211
|
-
type: 'union',
|
|
212
|
-
members: individualItems.map((item) => ast.createSchema({ type: 'ref', name: item.name })),
|
|
213
|
-
})
|
|
214
|
-
const unionType = renderSchemaType({ schema: unionSchema, name: dataName })
|
|
215
|
-
return (
|
|
216
|
-
<>
|
|
217
|
-
{individualItems.map((item) => item.rendered)}
|
|
218
|
-
{unionType}
|
|
219
|
-
</>
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const requestType = buildRequestType()
|
|
224
|
-
|
|
225
|
-
const responseTypes = node.responses.map((res) =>
|
|
226
|
-
renderSchemaType({
|
|
227
|
-
schema: res.schema,
|
|
228
|
-
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
229
|
-
keysToOmit: res.keysToOmit,
|
|
230
|
-
}),
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
const dataType = renderSchemaType({
|
|
234
|
-
schema: buildData({ ...node, parameters: params }, { resolver }),
|
|
235
|
-
name: resolver.resolveRequestConfigName(node),
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
const responsesType = renderSchemaType({
|
|
239
|
-
schema: buildResponses(node, { resolver }),
|
|
240
|
-
name: resolver.resolveResponsesName(node),
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
function buildResponseType() {
|
|
244
|
-
if (!node.responses.some((res) => res.schema)) {
|
|
245
|
-
return null
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const responseName = resolver.resolveResponseName(node)
|
|
249
|
-
|
|
250
|
-
const responsesWithSchema = node.responses.filter((res) => res.schema)
|
|
251
|
-
const importedNames = new Set(
|
|
252
|
-
responsesWithSchema.flatMap((res) =>
|
|
253
|
-
res.schema
|
|
254
|
-
? adapter
|
|
255
|
-
.getImports(res.schema, (schemaName) => ({
|
|
256
|
-
name: resolveImportName(schemaName),
|
|
257
|
-
path: '',
|
|
258
|
-
}))
|
|
259
|
-
.flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
|
|
260
|
-
: [],
|
|
261
|
-
),
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
if (importedNames.has(responseName)) {
|
|
265
|
-
return null
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return renderSchemaType({
|
|
269
|
-
schema: {
|
|
270
|
-
...buildResponseUnion(node, { resolver })!,
|
|
271
|
-
description: 'Union of all possible responses',
|
|
272
|
-
},
|
|
273
|
-
name: responseName,
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const responseType = buildResponseType()
|
|
278
|
-
|
|
279
|
-
return (
|
|
280
|
-
<File
|
|
281
|
-
baseName={meta.file.baseName}
|
|
282
|
-
path={meta.file.path}
|
|
283
|
-
meta={meta.file.meta}
|
|
284
|
-
banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
|
|
285
|
-
footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
|
|
286
|
-
>
|
|
287
|
-
{paramTypes}
|
|
288
|
-
{responseTypes}
|
|
289
|
-
{requestType}
|
|
290
|
-
{dataType}
|
|
291
|
-
{responsesType}
|
|
292
|
-
{responseType}
|
|
293
|
-
</File>
|
|
294
|
-
)
|
|
295
|
-
},
|
|
296
|
-
})
|
package/src/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { Enum } from './components/Enum.tsx'
|
|
2
|
-
export { Type } from './components/Type.tsx'
|
|
3
|
-
export { typeGenerator } from './generators/typeGenerator.tsx'
|
|
4
|
-
export { default, pluginTs, pluginTsName } from './plugin.ts'
|
|
5
|
-
export { functionPrinter } from './printers/functionPrinter.ts'
|
|
6
|
-
export type { PrinterTsFactory, PrinterTsNodes, PrinterTsOptions } from './printers/printerTs.ts'
|
|
7
|
-
export { printerTs } from './printers/printerTs.ts'
|
|
8
|
-
export { resolverTs } from './resolvers/resolverTs.ts'
|
|
9
|
-
export type { PluginTs, ResolverTs } from './types.ts'
|
package/src/plugin.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { camelCase } from '@internals/utils'
|
|
2
|
-
import { definePlugin, type Group } from '@kubb/core'
|
|
3
|
-
import { typeGenerator } from './generators/typeGenerator.tsx'
|
|
4
|
-
import { resolverTs } from './resolvers/resolverTs.ts'
|
|
5
|
-
import type { PluginTs } from './types.ts'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Canonical plugin name for `@kubb/plugin-ts`, used to identify the plugin in driver lookups and warnings.
|
|
9
|
-
*/
|
|
10
|
-
export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The `@kubb/plugin-ts` plugin factory.
|
|
14
|
-
*
|
|
15
|
-
* Generates TypeScript type declarations from an OpenAPI/AST `RootNode`.
|
|
16
|
-
* Walks schemas and operations, delegates rendering to the active generators,
|
|
17
|
-
* and writes barrel files based on `output.barrelType`.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* import pluginTs from '@kubb/plugin-ts'
|
|
22
|
-
*
|
|
23
|
-
* export default defineConfig({
|
|
24
|
-
* plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
|
|
25
|
-
* })
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export const pluginTs = definePlugin<PluginTs>((options) => {
|
|
29
|
-
const {
|
|
30
|
-
output = { path: 'types', barrelType: 'named' },
|
|
31
|
-
group,
|
|
32
|
-
exclude = [],
|
|
33
|
-
include,
|
|
34
|
-
override = [],
|
|
35
|
-
enumType = 'asConst',
|
|
36
|
-
enumTypeSuffix = 'Key',
|
|
37
|
-
enumKeyCasing = 'none',
|
|
38
|
-
optionalType = 'questionToken',
|
|
39
|
-
arrayType = 'array',
|
|
40
|
-
syntaxType = 'type',
|
|
41
|
-
paramsCasing,
|
|
42
|
-
printer,
|
|
43
|
-
resolver: userResolver,
|
|
44
|
-
transformer: userTransformer,
|
|
45
|
-
generators: userGenerators = [],
|
|
46
|
-
} = options
|
|
47
|
-
|
|
48
|
-
const groupConfig = group
|
|
49
|
-
? ({
|
|
50
|
-
...group,
|
|
51
|
-
name: (ctx) => {
|
|
52
|
-
if (group.type === 'path') {
|
|
53
|
-
return `${ctx.group.split('/')[1]}`
|
|
54
|
-
}
|
|
55
|
-
return `${camelCase(ctx.group)}Controller`
|
|
56
|
-
},
|
|
57
|
-
} satisfies Group)
|
|
58
|
-
: undefined
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
name: pluginTsName,
|
|
62
|
-
options,
|
|
63
|
-
hooks: {
|
|
64
|
-
'kubb:plugin:setup'(ctx) {
|
|
65
|
-
ctx.setOptions({
|
|
66
|
-
output,
|
|
67
|
-
exclude,
|
|
68
|
-
include,
|
|
69
|
-
override,
|
|
70
|
-
optionalType,
|
|
71
|
-
group: groupConfig,
|
|
72
|
-
arrayType,
|
|
73
|
-
enumType,
|
|
74
|
-
enumTypeSuffix,
|
|
75
|
-
enumKeyCasing,
|
|
76
|
-
syntaxType,
|
|
77
|
-
paramsCasing,
|
|
78
|
-
printer,
|
|
79
|
-
})
|
|
80
|
-
ctx.setResolver(userResolver ? { ...resolverTs, ...userResolver } : resolverTs)
|
|
81
|
-
if (userTransformer) {
|
|
82
|
-
ctx.setTransformer(userTransformer)
|
|
83
|
-
}
|
|
84
|
-
ctx.addGenerator(typeGenerator)
|
|
85
|
-
for (const gen of userGenerators) {
|
|
86
|
-
ctx.addGenerator(gen)
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
export default pluginTs
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { ast } from '@kubb/core'
|
|
2
|
-
import { PARAM_RANK } from '../constants.ts'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Maps each function-printer handler key to its concrete node type.
|
|
6
|
-
*/
|
|
7
|
-
export type FunctionNodeByType = {
|
|
8
|
-
functionParameter: ast.FunctionParameterNode
|
|
9
|
-
parameterGroup: ast.ParameterGroupNode
|
|
10
|
-
functionParameters: ast.FunctionParametersNode
|
|
11
|
-
paramsType: ast.ParamsTypeNode
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const kindToHandlerKey = {
|
|
15
|
-
FunctionParameter: 'functionParameter',
|
|
16
|
-
ParameterGroup: 'parameterGroup',
|
|
17
|
-
FunctionParameters: 'functionParameters',
|
|
18
|
-
ParamsType: 'paramsType',
|
|
19
|
-
} satisfies Record<string, ast.FunctionNodeType>
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Creates a function-parameter printer factory.
|
|
23
|
-
*
|
|
24
|
-
* Uses `createPrinterFactory` and dispatches handlers by `node.kind`
|
|
25
|
-
* (for function nodes) rather than by `node.type` (for schema nodes).
|
|
26
|
-
*/
|
|
27
|
-
export const defineFunctionPrinter = ast.createPrinterFactory<ast.FunctionParamNode, ast.FunctionNodeType, FunctionNodeByType>(
|
|
28
|
-
(node) => kindToHandlerKey[node.kind],
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
export type FunctionPrinterOptions = {
|
|
32
|
-
/**
|
|
33
|
-
* Rendering modes supported by `functionPrinter`.
|
|
34
|
-
*
|
|
35
|
-
* | Mode | Output example | Use case |
|
|
36
|
-
* |---------------|---------------------------------------------|---------------------------------|
|
|
37
|
-
* | `declaration` | `id: string, config: Config = {}` | Function parameter declaration |
|
|
38
|
-
* | `call` | `id, { method, url }` | Function call arguments |
|
|
39
|
-
* | `keys` | `{ id, config }` | Key names only (destructuring) |
|
|
40
|
-
* | `values` | `{ id: id, config: config }` | Key/value object entries |
|
|
41
|
-
*/
|
|
42
|
-
mode: 'declaration' | 'call' | 'keys' | 'values'
|
|
43
|
-
/**
|
|
44
|
-
* Optional transformation applied to every parameter name before printing.
|
|
45
|
-
*/
|
|
46
|
-
transformName?: (name: string) => string
|
|
47
|
-
/**
|
|
48
|
-
* Optional transformation applied to every type string before printing.
|
|
49
|
-
*/
|
|
50
|
-
transformType?: (type: string) => string
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
type DefaultPrinter = ast.PrinterFactoryOptions<'functionParameters', FunctionPrinterOptions, string>
|
|
54
|
-
|
|
55
|
-
function rank(param: ast.FunctionParameterNode | ast.ParameterGroupNode): number {
|
|
56
|
-
if (param.kind === 'ParameterGroup') {
|
|
57
|
-
if (param.default) return PARAM_RANK.withDefault
|
|
58
|
-
const isOptional = param.optional ?? param.properties.every((p) => p.optional || p.default !== undefined)
|
|
59
|
-
return isOptional ? PARAM_RANK.optional : PARAM_RANK.required
|
|
60
|
-
}
|
|
61
|
-
if (param.rest) return PARAM_RANK.rest
|
|
62
|
-
if (param.default) return PARAM_RANK.withDefault
|
|
63
|
-
return param.optional ? PARAM_RANK.optional : PARAM_RANK.required
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function sortParams(params: ReadonlyArray<ast.FunctionParameterNode | ast.ParameterGroupNode>): Array<ast.FunctionParameterNode | ast.ParameterGroupNode> {
|
|
67
|
-
return [...params].sort((a, b) => rank(a) - rank(b))
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function sortChildParams(params: Array<ast.FunctionParameterNode>): Array<ast.FunctionParameterNode> {
|
|
71
|
-
return [...params].sort((a, b) => rank(a) - rank(b))
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Default function-signature printer.
|
|
76
|
-
* Covers the four standard output modes used across Kubb plugins.
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```ts
|
|
80
|
-
* const printer = functionPrinter({ mode: 'declaration' })
|
|
81
|
-
*
|
|
82
|
-
* const sig = createFunctionParameters({
|
|
83
|
-
* params: [
|
|
84
|
-
* createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
|
|
85
|
-
* createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
|
|
86
|
-
* ],
|
|
87
|
-
* })
|
|
88
|
-
*
|
|
89
|
-
* printer.print(sig) // → "petId: string, config: Config = {}"
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
export const functionPrinter = defineFunctionPrinter<DefaultPrinter>((options) => ({
|
|
93
|
-
name: 'functionParameters',
|
|
94
|
-
options,
|
|
95
|
-
nodes: {
|
|
96
|
-
paramsType(node) {
|
|
97
|
-
if (node.kind !== 'ParamsType') return null
|
|
98
|
-
if (node.variant === 'member') {
|
|
99
|
-
return `${node.base}['${node.key}']`
|
|
100
|
-
}
|
|
101
|
-
if (node.variant === 'struct') {
|
|
102
|
-
const parts = node.properties.map((p) => {
|
|
103
|
-
const typeStr = this.transform(p.type)
|
|
104
|
-
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name)
|
|
105
|
-
return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`
|
|
106
|
-
})
|
|
107
|
-
return `{ ${parts.join('; ')} }`
|
|
108
|
-
}
|
|
109
|
-
if (node.variant === 'reference') {
|
|
110
|
-
return node.name
|
|
111
|
-
}
|
|
112
|
-
return null
|
|
113
|
-
},
|
|
114
|
-
functionParameter(node) {
|
|
115
|
-
const { mode, transformName, transformType } = this.options
|
|
116
|
-
const name = transformName ? transformName(node.name) : node.name
|
|
117
|
-
|
|
118
|
-
const rawType = node.type ? this.transform(node.type) : undefined
|
|
119
|
-
const type = rawType != null && transformType ? transformType(rawType) : rawType
|
|
120
|
-
|
|
121
|
-
if (mode === 'keys' || mode === 'values') {
|
|
122
|
-
return node.rest ? `...${name}` : name
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (mode === 'call') {
|
|
126
|
-
return node.rest ? `...${name}` : name
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (node.rest) {
|
|
130
|
-
return type ? `...${name}: ${type}` : `...${name}`
|
|
131
|
-
}
|
|
132
|
-
if (type) {
|
|
133
|
-
if (node.optional) return `${name}?: ${type}`
|
|
134
|
-
return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`
|
|
135
|
-
}
|
|
136
|
-
return node.default ? `${name} = ${node.default}` : name
|
|
137
|
-
},
|
|
138
|
-
parameterGroup(node) {
|
|
139
|
-
const { mode, transformName, transformType } = this.options
|
|
140
|
-
const sorted = sortChildParams(node.properties)
|
|
141
|
-
const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== undefined)
|
|
142
|
-
|
|
143
|
-
if (node.inline) {
|
|
144
|
-
return sorted
|
|
145
|
-
.map((p) => this.transform(p))
|
|
146
|
-
.filter(Boolean)
|
|
147
|
-
.join(', ')
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (mode === 'keys' || mode === 'values') {
|
|
151
|
-
const keys = sorted.map((p) => p.name).join(', ')
|
|
152
|
-
return `{ ${keys} }`
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (mode === 'call') {
|
|
156
|
-
const keys = sorted.map((p) => p.name).join(', ')
|
|
157
|
-
return `{ ${keys} }`
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const names = sorted.map((p) => {
|
|
161
|
-
const n = transformName ? transformName(p.name) : p.name
|
|
162
|
-
|
|
163
|
-
return n
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const nameStr = names.length ? `{ ${names.join(', ')} }` : undefined
|
|
167
|
-
if (!nameStr) return null
|
|
168
|
-
|
|
169
|
-
let typeAnnotation: string | undefined = node.type ? (this.transform(node.type) ?? undefined) : undefined
|
|
170
|
-
if (!typeAnnotation) {
|
|
171
|
-
const typeParts = sorted
|
|
172
|
-
.filter((p) => p.type)
|
|
173
|
-
.map((p) => {
|
|
174
|
-
const rawT = p.type ? this.transform(p.type) : undefined
|
|
175
|
-
const t = rawT != null && transformType ? transformType(rawT) : rawT
|
|
176
|
-
return p.optional || p.default !== undefined ? `${p.name}?: ${t}` : `${p.name}: ${t}`
|
|
177
|
-
})
|
|
178
|
-
typeAnnotation = typeParts.length ? `{ ${typeParts.join('; ')} }` : undefined
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (typeAnnotation) {
|
|
182
|
-
if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? '{}'}`
|
|
183
|
-
return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return node.default ? `${nameStr} = ${node.default}` : nameStr
|
|
187
|
-
},
|
|
188
|
-
functionParameters(node) {
|
|
189
|
-
const sorted = sortParams(node.params)
|
|
190
|
-
|
|
191
|
-
return sorted
|
|
192
|
-
.map((p) => this.transform(p))
|
|
193
|
-
.filter(Boolean)
|
|
194
|
-
.join(', ')
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
}))
|