@kubb/plugin-ts 5.0.0-beta.4 → 5.0.0-beta.56
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 +39 -22
- package/dist/index.cjs +513 -266
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +168 -144
- package/dist/index.js +506 -263
- package/dist/index.js.map +1 -1
- package/package.json +13 -22
- package/src/components/Enum.tsx +33 -29
- package/src/components/Type.tsx +6 -11
- package/src/constants.ts +7 -7
- package/src/factory.ts +58 -62
- package/src/generators/typeGenerator.tsx +118 -77
- package/src/plugin.ts +30 -28
- package/src/printers/functionPrinter.ts +2 -2
- package/src/printers/printerTs.ts +44 -54
- package/src/resolvers/resolverTs.ts +28 -25
- package/src/types.ts +113 -86
- package/src/utils.ts +24 -14
- package/extension.yaml +0 -632
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import "./chunk
|
|
2
|
-
import {
|
|
1
|
+
import { t as __name } from "./chunk-C0LytTxp.js";
|
|
2
|
+
import { extractRefName, jsStringEscape, stringify, trimQuotes } from "@kubb/ast/utils";
|
|
3
|
+
import { parserTs } from "@kubb/parser-ts";
|
|
3
4
|
import { File, jsxRenderer } from "@kubb/renderer-jsx";
|
|
4
5
|
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
5
|
-
import { isNumber } from "remeda";
|
|
6
6
|
import ts from "typescript";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
8
8
|
//#region ../../internals/utils/src/casing.ts
|
|
@@ -16,58 +16,41 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
|
16
16
|
function toCamelOrPascal(text, pascal) {
|
|
17
17
|
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
18
18
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
19
|
-
|
|
20
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
19
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
21
20
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
|
-
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
25
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
26
|
-
* Segments are joined with `/` to form a file path.
|
|
27
|
-
*
|
|
28
|
-
* Only splits on dots followed by a letter so that version numbers
|
|
29
|
-
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
30
|
-
*/
|
|
31
|
-
function applyToFileParts(text, transformPart) {
|
|
32
|
-
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
33
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
23
|
* Converts `text` to camelCase.
|
|
37
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
38
24
|
*
|
|
39
|
-
* @example
|
|
40
|
-
* camelCase('hello-world')
|
|
41
|
-
*
|
|
25
|
+
* @example Word boundaries
|
|
26
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
27
|
+
*
|
|
28
|
+
* @example With a prefix
|
|
29
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
42
30
|
*/
|
|
43
|
-
function camelCase(text, {
|
|
44
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
45
|
-
prefix,
|
|
46
|
-
suffix
|
|
47
|
-
} : {}));
|
|
31
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
48
32
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
49
33
|
}
|
|
50
34
|
/**
|
|
51
35
|
* Converts `text` to PascalCase.
|
|
52
|
-
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
53
36
|
*
|
|
54
|
-
* @example
|
|
55
|
-
* pascalCase('hello-world')
|
|
56
|
-
*
|
|
37
|
+
* @example Word boundaries
|
|
38
|
+
* `pascalCase('hello-world') // 'HelloWorld'`
|
|
39
|
+
*
|
|
40
|
+
* @example With a suffix
|
|
41
|
+
* `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
|
|
57
42
|
*/
|
|
58
|
-
function pascalCase(text, {
|
|
59
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
60
|
-
prefix,
|
|
61
|
-
suffix
|
|
62
|
-
}) : camelCase(part));
|
|
43
|
+
function pascalCase(text, { prefix = "", suffix = "" } = {}) {
|
|
63
44
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
64
45
|
}
|
|
65
46
|
/**
|
|
66
47
|
* Converts `text` to snake_case.
|
|
67
48
|
*
|
|
68
|
-
* @example
|
|
69
|
-
* snakeCase('helloWorld')
|
|
70
|
-
*
|
|
49
|
+
* @example From camelCase
|
|
50
|
+
* `snakeCase('helloWorld') // 'hello_world'`
|
|
51
|
+
*
|
|
52
|
+
* @example From mixed separators
|
|
53
|
+
* `snakeCase('Hello-World') // 'hello_world'`
|
|
71
54
|
*/
|
|
72
55
|
function snakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
73
56
|
return `${prefix} ${text} ${suffix}`.trim().replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s\-.]+/g, "_").replace(/[^a-zA-Z0-9_]/g, "").toLowerCase().split("_").filter(Boolean).join("_");
|
|
@@ -75,8 +58,8 @@ function snakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
|
75
58
|
/**
|
|
76
59
|
* Converts `text` to SCREAMING_SNAKE_CASE.
|
|
77
60
|
*
|
|
78
|
-
* @example
|
|
79
|
-
* screamingSnakeCase('helloWorld') // 'HELLO_WORLD'
|
|
61
|
+
* @example From camelCase
|
|
62
|
+
* `screamingSnakeCase('helloWorld') // 'HELLO_WORLD'`
|
|
80
63
|
*/
|
|
81
64
|
function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
82
65
|
return snakeCase(text, {
|
|
@@ -85,60 +68,152 @@ function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
|
85
68
|
}).toUpperCase();
|
|
86
69
|
}
|
|
87
70
|
//#endregion
|
|
88
|
-
//#region ../../internals/utils/src/
|
|
71
|
+
//#region ../../internals/utils/src/fs.ts
|
|
89
72
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
73
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
74
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
75
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
92
76
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
77
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
78
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
79
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
80
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
81
|
+
*
|
|
82
|
+
* @example Nested path from a dotted name
|
|
83
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
84
|
+
*
|
|
85
|
+
* @example PascalCase the final segment
|
|
86
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
87
|
+
*
|
|
88
|
+
* @example Suffix applied to the final segment only
|
|
89
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
90
|
+
*/
|
|
91
|
+
function toFilePath(name, caseLast = camelCase) {
|
|
92
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
93
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
104
94
|
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
105
97
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
* JavaScript and Java reserved words.
|
|
99
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
100
|
+
*/
|
|
101
|
+
const reservedWords = new Set([
|
|
102
|
+
"abstract",
|
|
103
|
+
"arguments",
|
|
104
|
+
"boolean",
|
|
105
|
+
"break",
|
|
106
|
+
"byte",
|
|
107
|
+
"case",
|
|
108
|
+
"catch",
|
|
109
|
+
"char",
|
|
110
|
+
"class",
|
|
111
|
+
"const",
|
|
112
|
+
"continue",
|
|
113
|
+
"debugger",
|
|
114
|
+
"default",
|
|
115
|
+
"delete",
|
|
116
|
+
"do",
|
|
117
|
+
"double",
|
|
118
|
+
"else",
|
|
119
|
+
"enum",
|
|
120
|
+
"eval",
|
|
121
|
+
"export",
|
|
122
|
+
"extends",
|
|
123
|
+
"false",
|
|
124
|
+
"final",
|
|
125
|
+
"finally",
|
|
126
|
+
"float",
|
|
127
|
+
"for",
|
|
128
|
+
"function",
|
|
129
|
+
"goto",
|
|
130
|
+
"if",
|
|
131
|
+
"implements",
|
|
132
|
+
"import",
|
|
133
|
+
"in",
|
|
134
|
+
"instanceof",
|
|
135
|
+
"int",
|
|
136
|
+
"interface",
|
|
137
|
+
"let",
|
|
138
|
+
"long",
|
|
139
|
+
"native",
|
|
140
|
+
"new",
|
|
141
|
+
"null",
|
|
142
|
+
"package",
|
|
143
|
+
"private",
|
|
144
|
+
"protected",
|
|
145
|
+
"public",
|
|
146
|
+
"return",
|
|
147
|
+
"short",
|
|
148
|
+
"static",
|
|
149
|
+
"super",
|
|
150
|
+
"switch",
|
|
151
|
+
"synchronized",
|
|
152
|
+
"this",
|
|
153
|
+
"throw",
|
|
154
|
+
"throws",
|
|
155
|
+
"transient",
|
|
156
|
+
"true",
|
|
157
|
+
"try",
|
|
158
|
+
"typeof",
|
|
159
|
+
"var",
|
|
160
|
+
"void",
|
|
161
|
+
"volatile",
|
|
162
|
+
"while",
|
|
163
|
+
"with",
|
|
164
|
+
"yield",
|
|
165
|
+
"Array",
|
|
166
|
+
"Date",
|
|
167
|
+
"hasOwnProperty",
|
|
168
|
+
"Infinity",
|
|
169
|
+
"isFinite",
|
|
170
|
+
"isNaN",
|
|
171
|
+
"isPrototypeOf",
|
|
172
|
+
"length",
|
|
173
|
+
"Math",
|
|
174
|
+
"name",
|
|
175
|
+
"NaN",
|
|
176
|
+
"Number",
|
|
177
|
+
"Object",
|
|
178
|
+
"prototype",
|
|
179
|
+
"String",
|
|
180
|
+
"toString",
|
|
181
|
+
"undefined",
|
|
182
|
+
"valueOf"
|
|
183
|
+
]);
|
|
184
|
+
/**
|
|
185
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
110
186
|
*
|
|
111
187
|
* @example
|
|
112
188
|
* ```ts
|
|
113
|
-
*
|
|
189
|
+
* isValidVarName('status') // true
|
|
190
|
+
* isValidVarName('class') // false (reserved word)
|
|
191
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
114
192
|
* ```
|
|
115
193
|
*/
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
case "\"":
|
|
120
|
-
case "'":
|
|
121
|
-
case "\\": return `\\${character}`;
|
|
122
|
-
case "\n": return "\\n";
|
|
123
|
-
case "\r": return "\\r";
|
|
124
|
-
case "\u2028": return "\\u2028";
|
|
125
|
-
case "\u2029": return "\\u2029";
|
|
126
|
-
default: return "";
|
|
127
|
-
}
|
|
128
|
-
});
|
|
194
|
+
function isValidVarName(name) {
|
|
195
|
+
if (!name || reservedWords.has(name)) return false;
|
|
196
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
129
197
|
}
|
|
130
|
-
//#endregion
|
|
131
|
-
//#region ../../internals/utils/src/object.ts
|
|
132
198
|
/**
|
|
133
|
-
*
|
|
199
|
+
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
200
|
+
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
201
|
+
*
|
|
202
|
+
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
203
|
+
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
204
|
+
* variable, type, or function names.
|
|
134
205
|
*
|
|
135
206
|
* @example
|
|
136
|
-
*
|
|
137
|
-
*
|
|
207
|
+
* ```ts
|
|
208
|
+
* ensureValidVarName('409') // '_409'
|
|
209
|
+
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
210
|
+
* ensureValidVarName('Pet') // 'Pet'
|
|
211
|
+
* ensureValidVarName('class') // '_class'
|
|
212
|
+
* ```
|
|
138
213
|
*/
|
|
139
|
-
function
|
|
140
|
-
if (
|
|
141
|
-
return
|
|
214
|
+
function ensureValidVarName(name) {
|
|
215
|
+
if (!name || isValidVarName(name)) return name;
|
|
216
|
+
return `_${name}`;
|
|
142
217
|
}
|
|
143
218
|
//#endregion
|
|
144
219
|
//#region src/constants.ts
|
|
@@ -151,26 +226,24 @@ const OPTIONAL_ADDS_UNDEFINED = new Set(["undefined", "questionTokenAndUndefined
|
|
|
151
226
|
*/
|
|
152
227
|
const OPTIONAL_ADDS_QUESTION_TOKEN = new Set(["questionToken", "questionTokenAndUndefined"]);
|
|
153
228
|
/**
|
|
154
|
-
* `
|
|
229
|
+
* `enum.type` values that append a `typeSuffix` to the generated enum type alias.
|
|
155
230
|
*/
|
|
156
|
-
const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst"
|
|
231
|
+
const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst"]);
|
|
157
232
|
/**
|
|
158
|
-
* `
|
|
233
|
+
* `enum.type` values that require a runtime value declaration (object, enum, or literal).
|
|
159
234
|
*/
|
|
160
235
|
const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set([
|
|
161
236
|
"enum",
|
|
162
237
|
"asConst",
|
|
163
|
-
"asPascalConst",
|
|
164
238
|
"constEnum",
|
|
165
239
|
"literal",
|
|
166
240
|
void 0
|
|
167
241
|
]);
|
|
168
242
|
/**
|
|
169
|
-
* `
|
|
243
|
+
* `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
|
|
170
244
|
*/
|
|
171
245
|
const ENUM_TYPES_WITH_TYPE_ONLY = new Set([
|
|
172
246
|
"asConst",
|
|
173
|
-
"asPascalConst",
|
|
174
247
|
"literal",
|
|
175
248
|
void 0
|
|
176
249
|
]);
|
|
@@ -186,6 +259,9 @@ const PARAM_RANK = {
|
|
|
186
259
|
//#endregion
|
|
187
260
|
//#region src/factory.ts
|
|
188
261
|
const { SyntaxKind, factory } = ts;
|
|
262
|
+
function isNumber(value) {
|
|
263
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
264
|
+
}
|
|
189
265
|
/**
|
|
190
266
|
* TypeScript AST modifiers for common keywords (async, export, const, static).
|
|
191
267
|
*/
|
|
@@ -203,10 +279,19 @@ const syntaxKind = {
|
|
|
203
279
|
literalType: SyntaxKind.LiteralType,
|
|
204
280
|
stringLiteral: SyntaxKind.StringLiteral
|
|
205
281
|
};
|
|
282
|
+
function isNonNullable$1(value) {
|
|
283
|
+
return value !== null && value !== void 0;
|
|
284
|
+
}
|
|
285
|
+
__name(isNonNullable$1, "isNonNullable");
|
|
206
286
|
function isValidIdentifier(str) {
|
|
207
287
|
if (!str.length || str.trim() !== str) return false;
|
|
208
|
-
|
|
209
|
-
|
|
288
|
+
let ch = str.codePointAt(0);
|
|
289
|
+
if (!ts.isIdentifierStart(ch, ts.ScriptTarget.Latest)) return false;
|
|
290
|
+
for (let i = ch > 65535 ? 2 : 1; i < str.length; i += ch > 65535 ? 2 : 1) {
|
|
291
|
+
ch = str.codePointAt(i);
|
|
292
|
+
if (!ts.isIdentifierPart(ch, ts.ScriptTarget.Latest)) return false;
|
|
293
|
+
}
|
|
294
|
+
return true;
|
|
210
295
|
}
|
|
211
296
|
function propertyName(name) {
|
|
212
297
|
if (typeof name === "string") return isValidIdentifier(name) ? factory.createIdentifier(name) : factory.createStringLiteral(name);
|
|
@@ -272,7 +357,7 @@ function createUnionDeclaration({ nodes, withParentheses }) {
|
|
|
272
357
|
* Supports optional markers, readonly modifiers, and type annotations.
|
|
273
358
|
*/
|
|
274
359
|
function createPropertySignature({ readOnly, modifiers = [], name, questionToken, type }) {
|
|
275
|
-
return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : void 0].filter(
|
|
360
|
+
return factory.createPropertySignature([...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : void 0].filter((modifier) => modifier !== void 0), propertyName(name), createQuestionToken(questionToken), type);
|
|
276
361
|
}
|
|
277
362
|
/**
|
|
278
363
|
* Creates a function parameter declaration with optional markers, rest parameters, and type annotations.
|
|
@@ -370,8 +455,8 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
370
455
|
}
|
|
371
456
|
if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
|
|
372
457
|
if (value) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
|
|
373
|
-
}).filter(
|
|
374
|
-
if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter(
|
|
458
|
+
}).filter((node) => node !== void 0)))];
|
|
459
|
+
if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value]) => {
|
|
375
460
|
let initializer = factory.createStringLiteral(value?.toString());
|
|
376
461
|
if (Number.parseInt(value.toString(), 10) === value && isNumber(Number.parseInt(value.toString(), 10))) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
|
|
377
462
|
else initializer = factory.createNumericLiteral(value);
|
|
@@ -384,7 +469,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
384
469
|
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
|
|
385
470
|
return factory.createEnumMember(propertyName(casingKey), initializer);
|
|
386
471
|
}
|
|
387
|
-
}).filter(
|
|
472
|
+
}).filter((member) => member !== void 0))];
|
|
388
473
|
const identifierName = name;
|
|
389
474
|
if (enums.length === 0) return [void 0, factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword))];
|
|
390
475
|
return [factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(identifierName), void 0, void 0, factory.createAsExpression(factory.createObjectLiteralExpression(enums.map(([key, value]) => {
|
|
@@ -396,7 +481,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
396
481
|
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
|
|
397
482
|
return factory.createPropertyAssignment(propertyName(casingKey), initializer);
|
|
398
483
|
}
|
|
399
|
-
}).filter(
|
|
484
|
+
}).filter((property) => property !== void 0), true), factory.createTypeReferenceNode(factory.createIdentifier("const"), void 0)))], ts.NodeFlags.Const)), factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0)), factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0))))];
|
|
400
485
|
}
|
|
401
486
|
/**
|
|
402
487
|
* Creates a TypeScript `Omit<T, Keys>` type reference node.
|
|
@@ -478,12 +563,10 @@ const createStringLiteral = factory.createStringLiteral;
|
|
|
478
563
|
* Creates an array type node (e.g., `T[]`).
|
|
479
564
|
*/
|
|
480
565
|
const createArrayTypeNode = factory.createArrayTypeNode;
|
|
481
|
-
factory.createParenthesizedType;
|
|
482
566
|
/**
|
|
483
567
|
* Creates a literal type node (e.g., `'hello'`, `42`, `true`).
|
|
484
568
|
*/
|
|
485
569
|
const createLiteralTypeNode = factory.createLiteralTypeNode;
|
|
486
|
-
factory.createNull;
|
|
487
570
|
/**
|
|
488
571
|
* Creates an identifier node.
|
|
489
572
|
*/
|
|
@@ -508,8 +591,6 @@ const createTrue = factory.createTrue;
|
|
|
508
591
|
* Creates a boolean false literal type node.
|
|
509
592
|
*/
|
|
510
593
|
const createFalse = factory.createFalse;
|
|
511
|
-
factory.createIndexedAccessTypeNode;
|
|
512
|
-
factory.createTypeOperatorNode;
|
|
513
594
|
/**
|
|
514
595
|
* Creates a prefix unary expression (e.g., negative numbers, logical not).
|
|
515
596
|
*/
|
|
@@ -536,14 +617,14 @@ function dateOrStringNode(node) {
|
|
|
536
617
|
* Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
|
|
537
618
|
*/
|
|
538
619
|
function buildMemberNodes(members, print) {
|
|
539
|
-
return (members ?? []).map(print).filter(
|
|
620
|
+
return (members ?? []).map(print).filter(isNonNullable$1);
|
|
540
621
|
}
|
|
541
622
|
/**
|
|
542
623
|
* Builds a TypeScript tuple type node from an array schema's `items`,
|
|
543
624
|
* applying min/max slice and optional/rest element rules.
|
|
544
625
|
*/
|
|
545
626
|
function buildTupleNode(node, print) {
|
|
546
|
-
let items = (node.items ?? []).map(print).filter(
|
|
627
|
+
let items = (node.items ?? []).map(print).filter(isNonNullable$1);
|
|
547
628
|
const restNode = node.rest ? print(node.rest) ?? void 0 : void 0;
|
|
548
629
|
const { min, max } = node;
|
|
549
630
|
if (max !== void 0) {
|
|
@@ -590,58 +671,61 @@ function buildIndexSignatures(node, propertyCount, print) {
|
|
|
590
671
|
* Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
|
|
591
672
|
*
|
|
592
673
|
* The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
|
|
593
|
-
* valid TypeScript identifier. The resolver normalizes it
|
|
594
|
-
*
|
|
674
|
+
* valid TypeScript identifier. The resolver normalizes it. For inline enum properties the adapter
|
|
675
|
+
* already emits a PascalCase+suffix name, so resolution is typically a no-op.
|
|
676
|
+
*
|
|
677
|
+
* When `constCasing` is `'pascalCase'` and `typeSuffix` is empty, the const and the type
|
|
678
|
+
* resolve to the same name, which TypeScript merges into a single value+type declaration.
|
|
595
679
|
*/
|
|
596
|
-
function getEnumNames({ node,
|
|
680
|
+
function getEnumNames({ node, enum: enumOptions, resolver }) {
|
|
597
681
|
const resolved = resolver.default(node.name, "type");
|
|
598
682
|
return {
|
|
599
|
-
enumName:
|
|
600
|
-
typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
683
|
+
enumName: enumOptions.constCasing === "pascalCase" ? resolved : camelCase(node.name),
|
|
684
|
+
typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolved
|
|
601
685
|
};
|
|
602
686
|
}
|
|
603
687
|
/**
|
|
604
688
|
* Renders the enum declaration(s) for a single named `EnumSchemaNode`.
|
|
605
689
|
*
|
|
606
|
-
* Depending on `
|
|
607
|
-
* - A runtime object (`asConst`
|
|
690
|
+
* Depending on `enum.type` this may emit:
|
|
691
|
+
* - A runtime object (`asConst`) plus a `typeof` type alias
|
|
608
692
|
* - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
|
|
609
693
|
* - A union literal type alias (`literal`)
|
|
610
694
|
*
|
|
611
695
|
* The emitted `File.Source` nodes carry the resolved names so that the barrel
|
|
612
696
|
* index picks up the correct export identifiers.
|
|
613
697
|
*/
|
|
614
|
-
function Enum({ node,
|
|
698
|
+
function Enum({ node, enum: enumOptions, resolver }) {
|
|
615
699
|
const { enumName, typeName } = getEnumNames({
|
|
616
700
|
node,
|
|
617
|
-
|
|
618
|
-
enumTypeSuffix,
|
|
701
|
+
enum: enumOptions,
|
|
619
702
|
resolver
|
|
620
703
|
});
|
|
621
704
|
const [nameNode, typeNode] = createEnumDeclaration({
|
|
622
705
|
name: enumName,
|
|
623
706
|
typeName,
|
|
624
707
|
enums: node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [trimQuotes(v.toString()), v]) ?? [],
|
|
625
|
-
type:
|
|
626
|
-
enumKeyCasing
|
|
708
|
+
type: enumOptions.type,
|
|
709
|
+
enumKeyCasing: enumOptions.keyCasing
|
|
627
710
|
});
|
|
711
|
+
const namesMerge = !!nameNode && enumName === typeName;
|
|
628
712
|
return /* @__PURE__ */ jsxs(Fragment, { children: [nameNode && /* @__PURE__ */ jsx(File.Source, {
|
|
629
713
|
name: enumName,
|
|
630
714
|
isExportable: true,
|
|
631
715
|
isIndexable: true,
|
|
632
716
|
isTypeOnly: false,
|
|
633
|
-
children:
|
|
717
|
+
children: parserTs.print(nameNode)
|
|
634
718
|
}), /* @__PURE__ */ jsx(File.Source, {
|
|
635
719
|
name: typeName,
|
|
636
|
-
isIndexable:
|
|
637
|
-
isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(
|
|
638
|
-
isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(
|
|
639
|
-
children:
|
|
720
|
+
isIndexable: !namesMerge,
|
|
721
|
+
isExportable: !namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type),
|
|
722
|
+
isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type),
|
|
723
|
+
children: parserTs.print(typeNode)
|
|
640
724
|
})] });
|
|
641
725
|
}
|
|
642
726
|
//#endregion
|
|
643
727
|
//#region src/components/Type.tsx
|
|
644
|
-
function Type({ name, node, printer,
|
|
728
|
+
function Type({ name, node, printer, enum: enumOptions, resolver }) {
|
|
645
729
|
const enumSchemaNodes = ast.collect(node, { schema(n) {
|
|
646
730
|
const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
|
|
647
731
|
if (enumNode?.name) return enumNode;
|
|
@@ -653,19 +737,16 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
|
|
|
653
737
|
node,
|
|
654
738
|
...getEnumNames({
|
|
655
739
|
node,
|
|
656
|
-
|
|
657
|
-
enumTypeSuffix,
|
|
740
|
+
enum: enumOptions,
|
|
658
741
|
resolver
|
|
659
742
|
})
|
|
660
743
|
};
|
|
661
744
|
});
|
|
662
|
-
const shouldExportEnums =
|
|
663
|
-
const shouldExportType =
|
|
745
|
+
const shouldExportEnums = enumOptions.type !== "inlineLiteral";
|
|
746
|
+
const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
|
|
664
747
|
return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
|
|
665
748
|
node,
|
|
666
|
-
|
|
667
|
-
enumTypeSuffix,
|
|
668
|
-
enumKeyCasing,
|
|
749
|
+
enum: enumOptions,
|
|
669
750
|
resolver
|
|
670
751
|
}, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
|
|
671
752
|
name,
|
|
@@ -676,6 +757,96 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
|
|
|
676
757
|
})] });
|
|
677
758
|
}
|
|
678
759
|
//#endregion
|
|
760
|
+
//#region ../../internals/shared/src/operation.ts
|
|
761
|
+
/**
|
|
762
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
763
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
764
|
+
*/
|
|
765
|
+
function getContentTypeSuffix(contentType) {
|
|
766
|
+
const baseType = contentType.split(";")[0].trim();
|
|
767
|
+
if (baseType === "application/json") return "Json";
|
|
768
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
769
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
770
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
771
|
+
if (parts.length === 0) return "Unknown";
|
|
772
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
776
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
777
|
+
*/
|
|
778
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
779
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
780
|
+
return baseName + suffix;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
784
|
+
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
785
|
+
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
786
|
+
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
787
|
+
*/
|
|
788
|
+
function resolveContentTypeVariants(entries, baseName) {
|
|
789
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
790
|
+
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
791
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
792
|
+
let suffix = baseSuffix;
|
|
793
|
+
let name = getPerContentTypeName(baseName, suffix);
|
|
794
|
+
let counter = 2;
|
|
795
|
+
while (usedNames.has(name)) {
|
|
796
|
+
suffix = `${baseSuffix}${counter++}`;
|
|
797
|
+
name = getPerContentTypeName(baseName, suffix);
|
|
798
|
+
}
|
|
799
|
+
usedNames.add(name);
|
|
800
|
+
return {
|
|
801
|
+
name,
|
|
802
|
+
suffix,
|
|
803
|
+
schema: entry.schema,
|
|
804
|
+
keysToOmit: entry.keysToOmit,
|
|
805
|
+
contentType: entry.contentType
|
|
806
|
+
};
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
function getOperationParameters(node, options = {}) {
|
|
810
|
+
const params = ast.caseParams(node.parameters, options.paramsCasing);
|
|
811
|
+
return {
|
|
812
|
+
path: params.filter((param) => param.in === "path"),
|
|
813
|
+
query: params.filter((param) => param.in === "query"),
|
|
814
|
+
header: params.filter((param) => param.in === "header"),
|
|
815
|
+
cookie: params.filter((param) => param.in === "cookie")
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region ../../internals/shared/src/group.ts
|
|
820
|
+
/**
|
|
821
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
822
|
+
* shared default naming so every plugin groups output consistently:
|
|
823
|
+
*
|
|
824
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
825
|
+
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
826
|
+
*
|
|
827
|
+
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
828
|
+
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
829
|
+
* per-plugin convention.
|
|
830
|
+
*
|
|
831
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* ```ts
|
|
835
|
+
* createGroupConfig(group) // shared across every plugin
|
|
836
|
+
* ```
|
|
837
|
+
*/
|
|
838
|
+
function createGroupConfig(group) {
|
|
839
|
+
if (!group) return null;
|
|
840
|
+
const defaultName = (ctx) => {
|
|
841
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
842
|
+
return camelCase(ctx.group);
|
|
843
|
+
};
|
|
844
|
+
return {
|
|
845
|
+
...group,
|
|
846
|
+
name: group.name ? group.name : defaultName
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
//#endregion
|
|
679
850
|
//#region src/utils.ts
|
|
680
851
|
/**
|
|
681
852
|
* Collects JSDoc annotation strings for a schema node.
|
|
@@ -687,15 +858,18 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
|
|
|
687
858
|
function buildPropertyJSDocComments(schema) {
|
|
688
859
|
const meta = ast.syncSchemaRef(schema);
|
|
689
860
|
const isArray = meta?.primitive === "array";
|
|
861
|
+
const hasDescription = meta && "description" in meta && meta.description;
|
|
862
|
+
const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
|
|
690
863
|
return [
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
!isArray && meta && "
|
|
695
|
-
meta && "
|
|
696
|
-
meta && "
|
|
697
|
-
meta && "
|
|
698
|
-
meta && "
|
|
864
|
+
hasDescription ? `@description ${jsStringEscape(meta.description)}` : null,
|
|
865
|
+
...formatComment,
|
|
866
|
+
meta && "deprecated" in meta && meta.deprecated ? "@deprecated" : null,
|
|
867
|
+
!isArray && meta && "min" in meta && meta.min !== void 0 ? `@minLength ${meta.min}` : null,
|
|
868
|
+
!isArray && meta && "max" in meta && meta.max !== void 0 ? `@maxLength ${meta.max}` : null,
|
|
869
|
+
meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
|
|
870
|
+
meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : null,
|
|
871
|
+
meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : null,
|
|
872
|
+
meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, "optional" in schema && schema.optional ? " | undefined" : null].filter(Boolean).join("") : null
|
|
699
873
|
].filter(Boolean);
|
|
700
874
|
}
|
|
701
875
|
function buildParams(node, { params, resolver }) {
|
|
@@ -712,9 +886,7 @@ function buildParams(node, { params, resolver }) {
|
|
|
712
886
|
});
|
|
713
887
|
}
|
|
714
888
|
function buildData(node, { resolver }) {
|
|
715
|
-
const pathParams
|
|
716
|
-
const queryParams = node.parameters.filter((p) => p.in === "query");
|
|
717
|
-
const headerParams = node.parameters.filter((p) => p.in === "header");
|
|
889
|
+
const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
|
|
718
890
|
return ast.createSchema({
|
|
719
891
|
type: "object",
|
|
720
892
|
deprecated: node.deprecated,
|
|
@@ -796,7 +968,7 @@ function buildResponses(node, { resolver }) {
|
|
|
796
968
|
});
|
|
797
969
|
}
|
|
798
970
|
function buildResponseUnion(node, { resolver }) {
|
|
799
|
-
const responsesWithSchema = node.responses.filter((res) => res.schema);
|
|
971
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
800
972
|
if (responsesWithSchema.length === 0) return null;
|
|
801
973
|
return ast.createSchema({
|
|
802
974
|
type: "union",
|
|
@@ -808,6 +980,9 @@ function buildResponseUnion(node, { resolver }) {
|
|
|
808
980
|
}
|
|
809
981
|
//#endregion
|
|
810
982
|
//#region src/printers/printerTs.ts
|
|
983
|
+
function isNonNullable(value) {
|
|
984
|
+
return value !== null && value !== void 0;
|
|
985
|
+
}
|
|
811
986
|
/**
|
|
812
987
|
* TypeScript type printer built with `definePrinter`.
|
|
813
988
|
*
|
|
@@ -821,13 +996,13 @@ function buildResponseUnion(node, { resolver }) {
|
|
|
821
996
|
*
|
|
822
997
|
* @example Raw type node (no `typeName`)
|
|
823
998
|
* ```ts
|
|
824
|
-
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array',
|
|
999
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
|
|
825
1000
|
* const typeNode = printer.print(schemaNode) // ts.TypeNode
|
|
826
1001
|
* ```
|
|
827
1002
|
*
|
|
828
1003
|
* @example Full declaration (with `typeName`)
|
|
829
1004
|
* ```ts
|
|
830
|
-
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array',
|
|
1005
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
|
|
831
1006
|
* const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
|
|
832
1007
|
* ```
|
|
833
1008
|
*/
|
|
@@ -860,17 +1035,17 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
860
1035
|
date: dateOrStringNode,
|
|
861
1036
|
time: dateOrStringNode,
|
|
862
1037
|
ref(node) {
|
|
863
|
-
if (!node.name) return;
|
|
864
|
-
const refName = node.ref ?
|
|
865
|
-
return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.
|
|
1038
|
+
if (!node.name) return null;
|
|
1039
|
+
const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
|
|
1040
|
+
return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
|
|
866
1041
|
},
|
|
867
1042
|
enum(node) {
|
|
868
1043
|
const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
|
|
869
|
-
if (this.options.
|
|
1044
|
+
if (this.options.enum.type === "inlineLiteral" || !node.name) return createUnionDeclaration({
|
|
870
1045
|
withParentheses: true,
|
|
871
|
-
nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(
|
|
1046
|
+
nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
|
|
872
1047
|
}) ?? void 0;
|
|
873
|
-
return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.
|
|
1048
|
+
return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enum.typeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
|
|
874
1049
|
},
|
|
875
1050
|
union(node) {
|
|
876
1051
|
const members = node.members ?? [];
|
|
@@ -886,7 +1061,7 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
886
1061
|
withParentheses: true
|
|
887
1062
|
});
|
|
888
1063
|
return this.transform(m);
|
|
889
|
-
}).filter(
|
|
1064
|
+
}).filter(isNonNullable)
|
|
890
1065
|
}) ?? void 0;
|
|
891
1066
|
return createUnionDeclaration({
|
|
892
1067
|
withParentheses: true,
|
|
@@ -897,16 +1072,16 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
897
1072
|
return createIntersectionDeclaration({
|
|
898
1073
|
withParentheses: true,
|
|
899
1074
|
nodes: buildMemberNodes(node.members, this.transform)
|
|
900
|
-
}) ??
|
|
1075
|
+
}) ?? null;
|
|
901
1076
|
},
|
|
902
1077
|
array(node) {
|
|
903
1078
|
return createArrayDeclaration({
|
|
904
|
-
nodes: (node.items ?? []).map((item) => this.transform(item)).filter(
|
|
1079
|
+
nodes: (node.items ?? []).map((item) => this.transform(item)).filter(isNonNullable),
|
|
905
1080
|
arrayType: this.options.arrayType
|
|
906
|
-
}) ??
|
|
1081
|
+
}) ?? null;
|
|
907
1082
|
},
|
|
908
1083
|
tuple(node) {
|
|
909
|
-
return buildTupleNode(node, this.transform);
|
|
1084
|
+
return buildTupleNode(node, this.transform) ?? null;
|
|
910
1085
|
},
|
|
911
1086
|
object(node) {
|
|
912
1087
|
const { transform, options } = this;
|
|
@@ -933,48 +1108,55 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
933
1108
|
},
|
|
934
1109
|
print(node) {
|
|
935
1110
|
const { name, syntaxType = "type", description, keysToOmit } = this.options;
|
|
936
|
-
|
|
937
|
-
if (!
|
|
1111
|
+
const transformed = this.transform(node);
|
|
1112
|
+
if (!transformed) return null;
|
|
938
1113
|
const meta = ast.syncSchemaRef(node);
|
|
939
1114
|
if (!name) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
return
|
|
1115
|
+
const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
|
|
1116
|
+
const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
|
|
1117
|
+
return parserTs.print(result);
|
|
943
1118
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1119
|
+
const inner = (() => {
|
|
1120
|
+
const omitted = keysToOmit?.length ? createOmitDeclaration({
|
|
1121
|
+
keys: keysToOmit,
|
|
1122
|
+
type: transformed,
|
|
1123
|
+
nonNullable: true
|
|
1124
|
+
}) : transformed;
|
|
1125
|
+
const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [omitted, keywordTypeNodes.null] }) : omitted;
|
|
1126
|
+
return meta.nullish || meta.optional ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
|
|
1127
|
+
})();
|
|
1128
|
+
const typeNode = createTypeDeclaration({
|
|
953
1129
|
name,
|
|
954
1130
|
isExportable: true,
|
|
955
1131
|
type: inner,
|
|
956
|
-
syntax:
|
|
1132
|
+
syntax: syntaxType === "type" || inner.kind === syntaxKind.union || !!keysToOmit?.length ? "type" : "interface",
|
|
957
1133
|
comments: buildPropertyJSDocComments({
|
|
958
1134
|
...meta,
|
|
959
1135
|
description
|
|
960
1136
|
})
|
|
961
|
-
})
|
|
1137
|
+
});
|
|
1138
|
+
return parserTs.print(typeNode);
|
|
962
1139
|
}
|
|
963
1140
|
};
|
|
964
1141
|
});
|
|
965
1142
|
//#endregion
|
|
966
1143
|
//#region src/generators/typeGenerator.tsx
|
|
1144
|
+
/**
|
|
1145
|
+
* Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
|
|
1146
|
+
* schema in the spec plus per-operation request, response, and parameter
|
|
1147
|
+
* types. Drop-replace with a custom `Generator<PluginTs>` to change how
|
|
1148
|
+
* TypeScript output is produced.
|
|
1149
|
+
*/
|
|
967
1150
|
const typeGenerator = defineGenerator({
|
|
968
1151
|
name: "typescript",
|
|
969
1152
|
renderer: jsxRenderer,
|
|
970
1153
|
schema(node, ctx) {
|
|
971
|
-
const {
|
|
1154
|
+
const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
|
|
972
1155
|
const { adapter, config, resolver, root } = ctx;
|
|
973
1156
|
if (!node.name) return;
|
|
974
|
-
const
|
|
975
|
-
const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name));
|
|
1157
|
+
const enumSchemaNames = new Set(ctx.meta.enumNames);
|
|
976
1158
|
function resolveImportName(schemaName) {
|
|
977
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
1159
|
+
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
|
|
978
1160
|
return resolver.resolveTypeName(schemaName);
|
|
979
1161
|
}
|
|
980
1162
|
const imports = adapter.getImports(node, (schemaName) => ({
|
|
@@ -985,26 +1167,25 @@ const typeGenerator = defineGenerator({
|
|
|
985
1167
|
}, {
|
|
986
1168
|
root,
|
|
987
1169
|
output,
|
|
988
|
-
group
|
|
1170
|
+
group: group ?? void 0
|
|
989
1171
|
}).path
|
|
990
1172
|
}));
|
|
991
1173
|
const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
|
|
992
1174
|
const meta = {
|
|
993
|
-
name: ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
1175
|
+
name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolver.resolveTypeName(node.name),
|
|
994
1176
|
file: resolver.resolveFile({
|
|
995
1177
|
name: node.name,
|
|
996
1178
|
extname: ".ts"
|
|
997
1179
|
}, {
|
|
998
1180
|
root,
|
|
999
1181
|
output,
|
|
1000
|
-
group
|
|
1182
|
+
group: group ?? void 0
|
|
1001
1183
|
})
|
|
1002
1184
|
};
|
|
1003
1185
|
const schemaPrinter = printerTs({
|
|
1004
1186
|
optionalType,
|
|
1005
1187
|
arrayType,
|
|
1006
|
-
|
|
1007
|
-
enumTypeSuffix,
|
|
1188
|
+
enum: enumOptions,
|
|
1008
1189
|
name: meta.name,
|
|
1009
1190
|
syntaxType,
|
|
1010
1191
|
description: node.description,
|
|
@@ -1016,15 +1197,23 @@ const typeGenerator = defineGenerator({
|
|
|
1016
1197
|
baseName: meta.file.baseName,
|
|
1017
1198
|
path: meta.file.path,
|
|
1018
1199
|
meta: meta.file.meta,
|
|
1019
|
-
banner: resolver.resolveBanner(
|
|
1200
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1020
1201
|
output,
|
|
1021
|
-
config
|
|
1202
|
+
config,
|
|
1203
|
+
file: {
|
|
1204
|
+
path: meta.file.path,
|
|
1205
|
+
baseName: meta.file.baseName
|
|
1206
|
+
}
|
|
1022
1207
|
}),
|
|
1023
|
-
footer: resolver.resolveFooter(
|
|
1208
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1024
1209
|
output,
|
|
1025
|
-
config
|
|
1210
|
+
config,
|
|
1211
|
+
file: {
|
|
1212
|
+
path: meta.file.path,
|
|
1213
|
+
baseName: meta.file.baseName
|
|
1214
|
+
}
|
|
1026
1215
|
}),
|
|
1027
|
-
children: [
|
|
1216
|
+
children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1028
1217
|
root: meta.file.path,
|
|
1029
1218
|
path: imp.path,
|
|
1030
1219
|
name: imp.name,
|
|
@@ -1036,18 +1225,15 @@ const typeGenerator = defineGenerator({
|
|
|
1036
1225
|
].join("-"))), /* @__PURE__ */ jsx(Type, {
|
|
1037
1226
|
name: meta.name,
|
|
1038
1227
|
node,
|
|
1039
|
-
|
|
1040
|
-
enumTypeSuffix,
|
|
1041
|
-
enumKeyCasing,
|
|
1228
|
+
enum: enumOptions,
|
|
1042
1229
|
resolver,
|
|
1043
1230
|
printer: schemaPrinter
|
|
1044
1231
|
})]
|
|
1045
1232
|
});
|
|
1046
1233
|
},
|
|
1047
1234
|
operation(node, ctx) {
|
|
1048
|
-
const {
|
|
1235
|
+
const { enum: enumOptions, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options;
|
|
1049
1236
|
const { adapter, config, resolver, root } = ctx;
|
|
1050
|
-
const mode = ctx.getMode(output);
|
|
1051
1237
|
const params = ast.caseParams(node.parameters, paramsCasing);
|
|
1052
1238
|
const meta = { file: resolver.resolveFile({
|
|
1053
1239
|
name: node.operationId,
|
|
@@ -1057,11 +1243,11 @@ const typeGenerator = defineGenerator({
|
|
|
1057
1243
|
}, {
|
|
1058
1244
|
root,
|
|
1059
1245
|
output,
|
|
1060
|
-
group
|
|
1246
|
+
group: group ?? void 0
|
|
1061
1247
|
}) };
|
|
1062
|
-
const enumSchemaNames = new Set(
|
|
1248
|
+
const enumSchemaNames = new Set(ctx.meta.enumNames);
|
|
1063
1249
|
function resolveImportName(schemaName) {
|
|
1064
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
1250
|
+
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
|
|
1065
1251
|
return resolver.resolveTypeName(schemaName);
|
|
1066
1252
|
}
|
|
1067
1253
|
function renderSchemaType({ schema, name, keysToOmit }) {
|
|
@@ -1074,14 +1260,13 @@ const typeGenerator = defineGenerator({
|
|
|
1074
1260
|
}, {
|
|
1075
1261
|
root,
|
|
1076
1262
|
output,
|
|
1077
|
-
group
|
|
1263
|
+
group: group ?? void 0
|
|
1078
1264
|
}).path
|
|
1079
1265
|
}));
|
|
1080
1266
|
const schemaPrinter = printerTs({
|
|
1081
1267
|
optionalType,
|
|
1082
1268
|
arrayType,
|
|
1083
|
-
|
|
1084
|
-
enumTypeSuffix,
|
|
1269
|
+
enum: enumOptions,
|
|
1085
1270
|
name,
|
|
1086
1271
|
syntaxType,
|
|
1087
1272
|
description: schema.description,
|
|
@@ -1090,7 +1275,7 @@ const typeGenerator = defineGenerator({
|
|
|
1090
1275
|
enumSchemaNames,
|
|
1091
1276
|
nodes: printer?.nodes
|
|
1092
1277
|
});
|
|
1093
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1278
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1094
1279
|
root: meta.file.path,
|
|
1095
1280
|
path: imp.path,
|
|
1096
1281
|
name: imp.name,
|
|
@@ -1102,30 +1287,68 @@ const typeGenerator = defineGenerator({
|
|
|
1102
1287
|
].join("-"))), /* @__PURE__ */ jsx(Type, {
|
|
1103
1288
|
name,
|
|
1104
1289
|
node: schema,
|
|
1105
|
-
|
|
1106
|
-
enumTypeSuffix,
|
|
1107
|
-
enumKeyCasing,
|
|
1290
|
+
enum: enumOptions,
|
|
1108
1291
|
resolver,
|
|
1109
1292
|
printer: schemaPrinter
|
|
1110
1293
|
})] });
|
|
1111
1294
|
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Emits an individual type per content type plus a union alias under `baseName`.
|
|
1297
|
+
* Shared by the request body and multi-content-type responses.
|
|
1298
|
+
*/
|
|
1299
|
+
function buildContentTypeVariants(entries, baseName, decorate) {
|
|
1300
|
+
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1301
|
+
const unionSchema = ast.createSchema({
|
|
1302
|
+
type: "union",
|
|
1303
|
+
members: variants.map((variant) => ast.createSchema({
|
|
1304
|
+
type: "ref",
|
|
1305
|
+
name: variant.name
|
|
1306
|
+
}))
|
|
1307
|
+
});
|
|
1308
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [variants.map((variant) => renderSchemaType({
|
|
1309
|
+
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
1310
|
+
name: variant.name,
|
|
1311
|
+
keysToOmit: variant.keysToOmit
|
|
1312
|
+
})), renderSchemaType({
|
|
1313
|
+
schema: unionSchema,
|
|
1314
|
+
name: baseName
|
|
1315
|
+
})] });
|
|
1316
|
+
}
|
|
1112
1317
|
const paramTypes = params.map((param) => renderSchemaType({
|
|
1113
1318
|
schema: param.schema,
|
|
1114
1319
|
name: resolver.resolveParamName(node, param)
|
|
1115
1320
|
}));
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1321
|
+
const requestBodyContent = node.requestBody?.content ?? [];
|
|
1322
|
+
function buildRequestType() {
|
|
1323
|
+
if (requestBodyContent.length === 0) return null;
|
|
1324
|
+
if (requestBodyContent.length === 1) {
|
|
1325
|
+
const entry = requestBodyContent[0];
|
|
1326
|
+
if (!entry.schema) return null;
|
|
1327
|
+
return renderSchemaType({
|
|
1328
|
+
schema: {
|
|
1329
|
+
...entry.schema,
|
|
1330
|
+
description: node.requestBody.description ?? entry.schema.description
|
|
1331
|
+
},
|
|
1332
|
+
name: resolver.resolveDataName(node),
|
|
1333
|
+
keysToOmit: entry.keysToOmit
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
|
|
1337
|
+
...schema,
|
|
1338
|
+
description: node.requestBody.description ?? schema.description
|
|
1339
|
+
}));
|
|
1340
|
+
}
|
|
1341
|
+
const requestType = buildRequestType();
|
|
1342
|
+
const responseTypes = node.responses.map((res) => {
|
|
1343
|
+
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
1344
|
+
if (variants.length > 1) return buildContentTypeVariants(variants, resolver.resolveResponseStatusName(node, res.statusCode));
|
|
1345
|
+
const primary = variants[0] ?? res.content?.[0];
|
|
1346
|
+
return renderSchemaType({
|
|
1347
|
+
schema: primary?.schema ?? null,
|
|
1348
|
+
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
1349
|
+
keysToOmit: primary?.keysToOmit
|
|
1350
|
+
});
|
|
1351
|
+
});
|
|
1129
1352
|
const dataType = renderSchemaType({
|
|
1130
1353
|
schema: buildData({
|
|
1131
1354
|
...node,
|
|
@@ -1137,14 +1360,15 @@ const typeGenerator = defineGenerator({
|
|
|
1137
1360
|
schema: buildResponses(node, { resolver }),
|
|
1138
1361
|
name: resolver.resolveResponsesName(node)
|
|
1139
1362
|
});
|
|
1140
|
-
|
|
1141
|
-
|
|
1363
|
+
function buildResponseType() {
|
|
1364
|
+
const hasSchema = (res) => (res.content ?? []).some((entry) => entry.schema);
|
|
1365
|
+
if (!node.responses.some(hasSchema)) return null;
|
|
1142
1366
|
const responseName = resolver.resolveResponseName(node);
|
|
1143
|
-
const responsesWithSchema = node.responses.filter(
|
|
1144
|
-
if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(
|
|
1367
|
+
const responsesWithSchema = node.responses.filter(hasSchema);
|
|
1368
|
+
if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
|
|
1145
1369
|
name: resolveImportName(schemaName),
|
|
1146
1370
|
path: ""
|
|
1147
|
-
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseName)) return null;
|
|
1371
|
+
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(responseName)) return null;
|
|
1148
1372
|
return renderSchemaType({
|
|
1149
1373
|
schema: {
|
|
1150
1374
|
...buildResponseUnion(node, { resolver }),
|
|
@@ -1152,18 +1376,27 @@ const typeGenerator = defineGenerator({
|
|
|
1152
1376
|
},
|
|
1153
1377
|
name: responseName
|
|
1154
1378
|
});
|
|
1155
|
-
}
|
|
1379
|
+
}
|
|
1380
|
+
const responseType = buildResponseType();
|
|
1156
1381
|
return /* @__PURE__ */ jsxs(File, {
|
|
1157
1382
|
baseName: meta.file.baseName,
|
|
1158
1383
|
path: meta.file.path,
|
|
1159
1384
|
meta: meta.file.meta,
|
|
1160
|
-
banner: resolver.resolveBanner(
|
|
1385
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1161
1386
|
output,
|
|
1162
|
-
config
|
|
1387
|
+
config,
|
|
1388
|
+
file: {
|
|
1389
|
+
path: meta.file.path,
|
|
1390
|
+
baseName: meta.file.baseName
|
|
1391
|
+
}
|
|
1163
1392
|
}),
|
|
1164
|
-
footer: resolver.resolveFooter(
|
|
1393
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1165
1394
|
output,
|
|
1166
|
-
config
|
|
1395
|
+
config,
|
|
1396
|
+
file: {
|
|
1397
|
+
path: meta.file.path,
|
|
1398
|
+
baseName: meta.file.baseName
|
|
1399
|
+
}
|
|
1167
1400
|
}),
|
|
1168
1401
|
children: [
|
|
1169
1402
|
paramTypes,
|
|
@@ -1179,101 +1412,113 @@ const typeGenerator = defineGenerator({
|
|
|
1179
1412
|
//#endregion
|
|
1180
1413
|
//#region src/resolvers/resolverTs.ts
|
|
1181
1414
|
/**
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1415
|
+
* Default resolver used by `@kubb/plugin-ts`. Decides the names and file paths
|
|
1416
|
+
* for every generated TypeScript type. Import this in other plugins that need
|
|
1417
|
+
* to reference the exact names `plugin-ts` produces without duplicating the
|
|
1418
|
+
* casing/file-layout rules.
|
|
1185
1419
|
*
|
|
1186
|
-
* The `default` method is
|
|
1187
|
-
*
|
|
1420
|
+
* The `default` method is supplied by `defineResolver`. It uses PascalCase for
|
|
1421
|
+
* type names and PascalCase file paths (dotted names become `/`-joined) for files.
|
|
1188
1422
|
*
|
|
1189
|
-
* @example
|
|
1423
|
+
* @example Resolve a type and file name
|
|
1190
1424
|
* ```ts
|
|
1191
|
-
* import {
|
|
1425
|
+
* import { resolverTs } from '@kubb/plugin-ts'
|
|
1192
1426
|
*
|
|
1193
|
-
*
|
|
1194
|
-
*
|
|
1195
|
-
*
|
|
1427
|
+
* resolverTs.default('list pets', 'type') // 'ListPets'
|
|
1428
|
+
* resolverTs.resolvePathName('list pets', 'file') // 'ListPets'
|
|
1429
|
+
* resolverTs.resolveResponseStatusName(node, 200) // 'ListPetsStatus200'
|
|
1196
1430
|
* ```
|
|
1197
1431
|
*/
|
|
1198
|
-
const resolverTs = defineResolver((
|
|
1432
|
+
const resolverTs = defineResolver(() => {
|
|
1199
1433
|
return {
|
|
1200
1434
|
name: "default",
|
|
1201
1435
|
pluginName: "plugin-ts",
|
|
1202
1436
|
default(name, type) {
|
|
1203
|
-
|
|
1437
|
+
if (type === "file") return toFilePath(name, pascalCase);
|
|
1438
|
+
return ensureValidVarName(pascalCase(name));
|
|
1204
1439
|
},
|
|
1205
1440
|
resolveTypeName(name) {
|
|
1206
|
-
return pascalCase(name);
|
|
1441
|
+
return ensureValidVarName(pascalCase(name));
|
|
1207
1442
|
},
|
|
1208
1443
|
resolvePathName(name, type) {
|
|
1209
|
-
|
|
1444
|
+
if (type === "file") return toFilePath(name, pascalCase);
|
|
1445
|
+
return ensureValidVarName(pascalCase(name));
|
|
1210
1446
|
},
|
|
1211
1447
|
resolveParamName(node, param) {
|
|
1212
|
-
return
|
|
1448
|
+
return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
|
|
1213
1449
|
},
|
|
1214
1450
|
resolveResponseStatusName(node, statusCode) {
|
|
1215
|
-
return
|
|
1451
|
+
return this.resolveTypeName(`${node.operationId} Status ${statusCode}`);
|
|
1216
1452
|
},
|
|
1217
1453
|
resolveDataName(node) {
|
|
1218
|
-
return
|
|
1454
|
+
return this.resolveTypeName(`${node.operationId} Data`);
|
|
1219
1455
|
},
|
|
1220
1456
|
resolveRequestConfigName(node) {
|
|
1221
|
-
return
|
|
1457
|
+
return this.resolveTypeName(`${node.operationId} RequestConfig`);
|
|
1222
1458
|
},
|
|
1223
1459
|
resolveResponsesName(node) {
|
|
1224
|
-
return
|
|
1460
|
+
return this.resolveTypeName(`${node.operationId} Responses`);
|
|
1225
1461
|
},
|
|
1226
1462
|
resolveResponseName(node) {
|
|
1227
|
-
return
|
|
1463
|
+
return this.resolveTypeName(`${node.operationId} Response`);
|
|
1228
1464
|
},
|
|
1229
1465
|
resolveEnumKeyName(node, enumTypeSuffix = "key") {
|
|
1230
|
-
return `${
|
|
1466
|
+
return `${this.resolveTypeName(node.name ?? "")}${enumTypeSuffix}`;
|
|
1231
1467
|
},
|
|
1232
1468
|
resolvePathParamsName(node, param) {
|
|
1233
|
-
return
|
|
1469
|
+
return this.resolveParamName(node, param);
|
|
1234
1470
|
},
|
|
1235
1471
|
resolveQueryParamsName(node, param) {
|
|
1236
|
-
return
|
|
1472
|
+
return this.resolveParamName(node, param);
|
|
1237
1473
|
},
|
|
1238
1474
|
resolveHeaderParamsName(node, param) {
|
|
1239
|
-
return
|
|
1475
|
+
return this.resolveParamName(node, param);
|
|
1240
1476
|
}
|
|
1241
1477
|
};
|
|
1242
1478
|
});
|
|
1243
1479
|
//#endregion
|
|
1244
1480
|
//#region src/plugin.ts
|
|
1245
1481
|
/**
|
|
1246
|
-
* Canonical plugin name for `@kubb/plugin-ts
|
|
1482
|
+
* Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
|
|
1483
|
+
* cross-plugin dependency references.
|
|
1247
1484
|
*/
|
|
1248
1485
|
const pluginTsName = "plugin-ts";
|
|
1249
1486
|
/**
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1254
|
-
* and writes barrel files based on `output.barrelType`.
|
|
1487
|
+
* Generates TypeScript `type` aliases and `interface` declarations from an
|
|
1488
|
+
* OpenAPI spec. The foundation that every other Kubb plugin builds on:
|
|
1489
|
+
* clients, query hooks, mocks, and validators all reference the names this
|
|
1490
|
+
* plugin produces.
|
|
1255
1491
|
*
|
|
1256
1492
|
* @example
|
|
1257
1493
|
* ```ts
|
|
1258
|
-
* import
|
|
1494
|
+
* import { defineConfig } from 'kubb'
|
|
1495
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1259
1496
|
*
|
|
1260
1497
|
* export default defineConfig({
|
|
1261
|
-
*
|
|
1498
|
+
* input: { path: './petStore.yaml' },
|
|
1499
|
+
* output: { path: './src/gen' },
|
|
1500
|
+
* plugins: [
|
|
1501
|
+
* pluginTs({
|
|
1502
|
+
* output: { path: './types' },
|
|
1503
|
+
* enum: { type: 'asConst' },
|
|
1504
|
+
* optionalType: 'questionTokenAndUndefined',
|
|
1505
|
+
* }),
|
|
1506
|
+
* ],
|
|
1262
1507
|
* })
|
|
1263
1508
|
* ```
|
|
1264
1509
|
*/
|
|
1265
1510
|
const pluginTs = definePlugin((options) => {
|
|
1266
1511
|
const { output = {
|
|
1267
1512
|
path: "types",
|
|
1268
|
-
|
|
1269
|
-
}, group, exclude = [], include, override = [],
|
|
1270
|
-
const groupConfig = group
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
}
|
|
1513
|
+
barrel: { type: "named" }
|
|
1514
|
+
}, group, exclude = [], include, override = [], enum: enumOptions = {}, optionalType = "questionToken", arrayType = "array", syntaxType = "type", paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
|
|
1515
|
+
const groupConfig = createGroupConfig(group);
|
|
1516
|
+
const resolvedEnum = {
|
|
1517
|
+
type: enumOptions.type ?? "asConst",
|
|
1518
|
+
constCasing: enumOptions.constCasing ?? "camelCase",
|
|
1519
|
+
typeSuffix: enumOptions.typeSuffix ?? "Key",
|
|
1520
|
+
keyCasing: enumOptions.keyCasing ?? "none"
|
|
1521
|
+
};
|
|
1277
1522
|
return {
|
|
1278
1523
|
name: pluginTsName,
|
|
1279
1524
|
options,
|
|
@@ -1286,9 +1531,7 @@ const pluginTs = definePlugin((options) => {
|
|
|
1286
1531
|
optionalType,
|
|
1287
1532
|
group: groupConfig,
|
|
1288
1533
|
arrayType,
|
|
1289
|
-
|
|
1290
|
-
enumTypeSuffix,
|
|
1291
|
-
enumKeyCasing,
|
|
1534
|
+
enum: resolvedEnum,
|
|
1292
1535
|
syntaxType,
|
|
1293
1536
|
paramsCasing,
|
|
1294
1537
|
printer
|
|
@@ -1328,10 +1571,10 @@ function rank(param) {
|
|
|
1328
1571
|
return param.optional ? PARAM_RANK.optional : PARAM_RANK.required;
|
|
1329
1572
|
}
|
|
1330
1573
|
function sortParams(params) {
|
|
1331
|
-
return
|
|
1574
|
+
return params.toSorted((a, b) => rank(a) - rank(b));
|
|
1332
1575
|
}
|
|
1333
1576
|
function sortChildParams(params) {
|
|
1334
|
-
return
|
|
1577
|
+
return params.toSorted((a, b) => rank(a) - rank(b));
|
|
1335
1578
|
}
|
|
1336
1579
|
/**
|
|
1337
1580
|
* Default function-signature printer.
|