@kubb/plugin-ts 5.0.0-beta.42 → 5.0.0-beta.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +244 -279
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +124 -110
- package/dist/index.js +242 -278
- package/dist/index.js.map +1 -1
- package/package.json +8 -16
- package/extension.yaml +0 -1080
- 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 -1003
- package/src/generators/typeGenerator.tsx +0 -294
- package/src/index.ts +0 -9
- package/src/plugin.ts +0 -92
- package/src/printers/functionPrinter.ts +0 -197
- package/src/printers/printerTs.ts +0 -326
- package/src/resolvers/resolverTs.ts +0 -69
- package/src/types.ts +0 -315
- package/src/utils.ts +0 -140
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { t as __name } from "./chunk-C0LytTxp.js";
|
|
2
|
+
import { caseParams, extractRefName, isStringType, jsStringEscape, mapSchemaItems, mapSchemaProperties, stringify, syncSchemaRef, trimQuotes } from "@kubb/ast/utils";
|
|
2
3
|
import { parserTs } from "@kubb/parser-ts";
|
|
3
|
-
import { File,
|
|
4
|
-
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
4
|
+
import { File, jsxRenderer } from "@kubb/renderer-jsx";
|
|
5
5
|
import ts from "typescript";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
7
|
+
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
7
8
|
//#region ../../internals/utils/src/casing.ts
|
|
8
9
|
/**
|
|
9
10
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -15,58 +16,41 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
|
15
16
|
function toCamelOrPascal(text, pascal) {
|
|
16
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) => {
|
|
17
18
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
18
|
-
|
|
19
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
19
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
20
20
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
24
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
25
|
-
* Segments are joined with `/` to form a file path.
|
|
26
|
-
*
|
|
27
|
-
* Only splits on dots followed by a letter so that version numbers
|
|
28
|
-
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
29
|
-
*/
|
|
30
|
-
function applyToFileParts(text, transformPart) {
|
|
31
|
-
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
32
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
23
|
* Converts `text` to camelCase.
|
|
36
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
37
24
|
*
|
|
38
|
-
* @example
|
|
39
|
-
* camelCase('hello-world')
|
|
40
|
-
*
|
|
25
|
+
* @example Word boundaries
|
|
26
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
27
|
+
*
|
|
28
|
+
* @example With a prefix
|
|
29
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
41
30
|
*/
|
|
42
|
-
function camelCase(text, {
|
|
43
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
44
|
-
prefix,
|
|
45
|
-
suffix
|
|
46
|
-
} : {}));
|
|
31
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
47
32
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
48
33
|
}
|
|
49
34
|
/**
|
|
50
35
|
* Converts `text` to PascalCase.
|
|
51
|
-
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
52
36
|
*
|
|
53
|
-
* @example
|
|
54
|
-
* pascalCase('hello-world')
|
|
55
|
-
*
|
|
37
|
+
* @example Word boundaries
|
|
38
|
+
* `pascalCase('hello-world') // 'HelloWorld'`
|
|
39
|
+
*
|
|
40
|
+
* @example With a suffix
|
|
41
|
+
* `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
|
|
56
42
|
*/
|
|
57
|
-
function pascalCase(text, {
|
|
58
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
|
|
59
|
-
prefix,
|
|
60
|
-
suffix
|
|
61
|
-
}) : camelCase(part));
|
|
43
|
+
function pascalCase(text, { prefix = "", suffix = "" } = {}) {
|
|
62
44
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
63
45
|
}
|
|
64
46
|
/**
|
|
65
47
|
* Converts `text` to snake_case.
|
|
66
48
|
*
|
|
67
|
-
* @example
|
|
68
|
-
* snakeCase('helloWorld')
|
|
69
|
-
*
|
|
49
|
+
* @example From camelCase
|
|
50
|
+
* `snakeCase('helloWorld') // 'hello_world'`
|
|
51
|
+
*
|
|
52
|
+
* @example From mixed separators
|
|
53
|
+
* `snakeCase('Hello-World') // 'hello_world'`
|
|
70
54
|
*/
|
|
71
55
|
function snakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
72
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("_");
|
|
@@ -74,8 +58,8 @@ function snakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
|
74
58
|
/**
|
|
75
59
|
* Converts `text` to SCREAMING_SNAKE_CASE.
|
|
76
60
|
*
|
|
77
|
-
* @example
|
|
78
|
-
* screamingSnakeCase('helloWorld') // 'HELLO_WORLD'
|
|
61
|
+
* @example From camelCase
|
|
62
|
+
* `screamingSnakeCase('helloWorld') // 'HELLO_WORLD'`
|
|
79
63
|
*/
|
|
80
64
|
function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
81
65
|
return snakeCase(text, {
|
|
@@ -84,60 +68,29 @@ function screamingSnakeCase(text, { prefix = "", suffix = "" } = {}) {
|
|
|
84
68
|
}).toUpperCase();
|
|
85
69
|
}
|
|
86
70
|
//#endregion
|
|
87
|
-
//#region ../../internals/utils/src/
|
|
71
|
+
//#region ../../internals/utils/src/fs.ts
|
|
88
72
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
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 `/`.
|
|
91
76
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
function trimQuotes(text) {
|
|
97
|
-
if (text.length >= 2) {
|
|
98
|
-
const first = text[0];
|
|
99
|
-
const last = text[text.length - 1];
|
|
100
|
-
if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
|
|
101
|
-
}
|
|
102
|
-
return text;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Escapes characters that are not allowed inside JS string literals.
|
|
106
|
-
* Handles quotes, backslashes, and Unicode line terminators (U+2028 / U+2029).
|
|
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.
|
|
107
81
|
*
|
|
108
|
-
* @
|
|
82
|
+
* @example Nested path from a dotted name
|
|
83
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
109
84
|
*
|
|
110
|
-
* @example
|
|
111
|
-
*
|
|
112
|
-
* jsStringEscape('say "hi"\nbye') // 'say \\"hi\\"\\nbye'
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
115
|
-
function jsStringEscape(input) {
|
|
116
|
-
return `${input}`.replace(/["'\\\n\r\u2028\u2029]/g, (character) => {
|
|
117
|
-
switch (character) {
|
|
118
|
-
case "\"":
|
|
119
|
-
case "'":
|
|
120
|
-
case "\\": return `\\${character}`;
|
|
121
|
-
case "\n": return "\\n";
|
|
122
|
-
case "\r": return "\\r";
|
|
123
|
-
case "\u2028": return "\\u2028";
|
|
124
|
-
case "\u2029": return "\\u2029";
|
|
125
|
-
default: return "";
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
//#endregion
|
|
130
|
-
//#region ../../internals/utils/src/object.ts
|
|
131
|
-
/**
|
|
132
|
-
* Serializes a primitive value to a JSON string literal, stripping any surrounding quote characters first.
|
|
85
|
+
* @example PascalCase the final segment
|
|
86
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
133
87
|
*
|
|
134
|
-
* @example
|
|
135
|
-
*
|
|
136
|
-
* stringify('"hello"') // '"hello"'
|
|
88
|
+
* @example Suffix applied to the final segment only
|
|
89
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
137
90
|
*/
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
return
|
|
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("/");
|
|
141
94
|
}
|
|
142
95
|
//#endregion
|
|
143
96
|
//#region ../../internals/utils/src/reserved.ts
|
|
@@ -273,26 +226,24 @@ const OPTIONAL_ADDS_UNDEFINED = new Set(["undefined", "questionTokenAndUndefined
|
|
|
273
226
|
*/
|
|
274
227
|
const OPTIONAL_ADDS_QUESTION_TOKEN = new Set(["questionToken", "questionTokenAndUndefined"]);
|
|
275
228
|
/**
|
|
276
|
-
* `
|
|
229
|
+
* `enum.type` values that append a `typeSuffix` to the generated enum type alias.
|
|
277
230
|
*/
|
|
278
|
-
const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst"
|
|
231
|
+
const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst"]);
|
|
279
232
|
/**
|
|
280
|
-
* `
|
|
233
|
+
* `enum.type` values that require a runtime value declaration (object, enum, or literal).
|
|
281
234
|
*/
|
|
282
235
|
const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set([
|
|
283
236
|
"enum",
|
|
284
237
|
"asConst",
|
|
285
|
-
"asPascalConst",
|
|
286
238
|
"constEnum",
|
|
287
239
|
"literal",
|
|
288
240
|
void 0
|
|
289
241
|
]);
|
|
290
242
|
/**
|
|
291
|
-
* `
|
|
243
|
+
* `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
|
|
292
244
|
*/
|
|
293
245
|
const ENUM_TYPES_WITH_TYPE_ONLY = new Set([
|
|
294
246
|
"asConst",
|
|
295
|
-
"asPascalConst",
|
|
296
247
|
"literal",
|
|
297
248
|
void 0
|
|
298
249
|
]);
|
|
@@ -503,7 +454,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
503
454
|
return factory.createLiteralTypeNode(factory.createNumericLiteral(value?.toString()));
|
|
504
455
|
}
|
|
505
456
|
if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
|
|
506
|
-
if (value) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
|
|
457
|
+
if (value !== null && value !== void 0) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
|
|
507
458
|
}).filter((node) => node !== void 0)))];
|
|
508
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]) => {
|
|
509
460
|
let initializer = factory.createStringLiteral(value?.toString());
|
|
@@ -514,7 +465,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
514
465
|
const casingKey = applyEnumKeyCasing(`${typeName}_${key}`, enumKeyCasing);
|
|
515
466
|
return factory.createEnumMember(propertyName(casingKey), initializer);
|
|
516
467
|
}
|
|
517
|
-
if (key) {
|
|
468
|
+
if (key !== null && key !== void 0) {
|
|
518
469
|
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
|
|
519
470
|
return factory.createEnumMember(propertyName(casingKey), initializer);
|
|
520
471
|
}
|
|
@@ -526,7 +477,7 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
|
|
|
526
477
|
if (isNumber(value)) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
|
|
527
478
|
else initializer = factory.createNumericLiteral(value);
|
|
528
479
|
if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
|
|
529
|
-
if (key) {
|
|
480
|
+
if (key !== null && key !== void 0) {
|
|
530
481
|
const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
|
|
531
482
|
return factory.createPropertyAssignment(propertyName(casingKey), initializer);
|
|
532
483
|
}
|
|
@@ -612,12 +563,10 @@ const createStringLiteral = factory.createStringLiteral;
|
|
|
612
563
|
* Creates an array type node (e.g., `T[]`).
|
|
613
564
|
*/
|
|
614
565
|
const createArrayTypeNode = factory.createArrayTypeNode;
|
|
615
|
-
factory.createParenthesizedType;
|
|
616
566
|
/**
|
|
617
567
|
* Creates a literal type node (e.g., `'hello'`, `42`, `true`).
|
|
618
568
|
*/
|
|
619
569
|
const createLiteralTypeNode = factory.createLiteralTypeNode;
|
|
620
|
-
factory.createNull;
|
|
621
570
|
/**
|
|
622
571
|
* Creates an identifier node.
|
|
623
572
|
*/
|
|
@@ -642,8 +591,6 @@ const createTrue = factory.createTrue;
|
|
|
642
591
|
* Creates a boolean false literal type node.
|
|
643
592
|
*/
|
|
644
593
|
const createFalse = factory.createFalse;
|
|
645
|
-
factory.createIndexedAccessTypeNode;
|
|
646
|
-
factory.createTypeOperatorNode;
|
|
647
594
|
/**
|
|
648
595
|
* Creates a prefix unary expression (e.g., negative numbers, logical not).
|
|
649
596
|
*/
|
|
@@ -691,12 +638,12 @@ function buildTupleNode(node, print) {
|
|
|
691
638
|
/**
|
|
692
639
|
* Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
|
|
693
640
|
*/
|
|
694
|
-
function buildPropertyType(schema, baseType, optionalType) {
|
|
641
|
+
function buildPropertyType(schema, baseType, optionalType, optional) {
|
|
695
642
|
const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType);
|
|
696
|
-
const meta =
|
|
643
|
+
const meta = syncSchemaRef(schema);
|
|
697
644
|
let type = baseType;
|
|
698
645
|
if (meta.nullable) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.null] });
|
|
699
|
-
if ((meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] });
|
|
646
|
+
if ((optional || meta.nullish || meta.optional) && addsUndefined) type = createUnionDeclaration({ nodes: [type, keywordTypeNodes.undefined] });
|
|
700
647
|
return type;
|
|
701
648
|
}
|
|
702
649
|
/**
|
|
@@ -724,41 +671,44 @@ function buildIndexSignatures(node, propertyCount, print) {
|
|
|
724
671
|
* Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
|
|
725
672
|
*
|
|
726
673
|
* The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
|
|
727
|
-
* valid TypeScript identifier. The resolver normalizes it
|
|
728
|
-
*
|
|
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.
|
|
729
679
|
*/
|
|
730
|
-
function getEnumNames({ node,
|
|
680
|
+
function getEnumNames({ node, enum: enumOptions, resolver }) {
|
|
731
681
|
const resolved = resolver.default(node.name, "type");
|
|
732
682
|
return {
|
|
733
|
-
enumName:
|
|
734
|
-
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
|
|
735
685
|
};
|
|
736
686
|
}
|
|
737
687
|
/**
|
|
738
688
|
* Renders the enum declaration(s) for a single named `EnumSchemaNode`.
|
|
739
689
|
*
|
|
740
|
-
* Depending on `
|
|
741
|
-
* - A runtime object (`asConst`
|
|
690
|
+
* Depending on `enum.type` this may emit:
|
|
691
|
+
* - A runtime object (`asConst`) plus a `typeof` type alias
|
|
742
692
|
* - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
|
|
743
693
|
* - A union literal type alias (`literal`)
|
|
744
694
|
*
|
|
745
695
|
* The emitted `File.Source` nodes carry the resolved names so that the barrel
|
|
746
696
|
* index picks up the correct export identifiers.
|
|
747
697
|
*/
|
|
748
|
-
function Enum({ node,
|
|
698
|
+
function Enum({ node, enum: enumOptions, resolver }) {
|
|
749
699
|
const { enumName, typeName } = getEnumNames({
|
|
750
700
|
node,
|
|
751
|
-
|
|
752
|
-
enumTypeSuffix,
|
|
701
|
+
enum: enumOptions,
|
|
753
702
|
resolver
|
|
754
703
|
});
|
|
755
704
|
const [nameNode, typeNode] = createEnumDeclaration({
|
|
756
705
|
name: enumName,
|
|
757
706
|
typeName,
|
|
758
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]) ?? [],
|
|
759
|
-
type:
|
|
760
|
-
enumKeyCasing
|
|
708
|
+
type: enumOptions.type,
|
|
709
|
+
enumKeyCasing: enumOptions.keyCasing
|
|
761
710
|
});
|
|
711
|
+
const namesMerge = !!nameNode && enumName === typeName;
|
|
762
712
|
return /* @__PURE__ */ jsxs(Fragment, { children: [nameNode && /* @__PURE__ */ jsx(File.Source, {
|
|
763
713
|
name: enumName,
|
|
764
714
|
isExportable: true,
|
|
@@ -767,15 +717,15 @@ function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }) {
|
|
|
767
717
|
children: parserTs.print(nameNode)
|
|
768
718
|
}), /* @__PURE__ */ jsx(File.Source, {
|
|
769
719
|
name: typeName,
|
|
770
|
-
isIndexable:
|
|
771
|
-
isExportable: ENUM_TYPES_WITH_RUNTIME_VALUE.has(
|
|
772
|
-
isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(
|
|
720
|
+
isIndexable: !namesMerge,
|
|
721
|
+
isExportable: !namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type),
|
|
722
|
+
isTypeOnly: ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type),
|
|
773
723
|
children: parserTs.print(typeNode)
|
|
774
724
|
})] });
|
|
775
725
|
}
|
|
776
726
|
//#endregion
|
|
777
727
|
//#region src/components/Type.tsx
|
|
778
|
-
function Type({ name, node, printer,
|
|
728
|
+
function Type({ name, node, printer, enum: enumOptions, resolver }) {
|
|
779
729
|
const enumSchemaNodes = ast.collect(node, { schema(n) {
|
|
780
730
|
const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
|
|
781
731
|
if (enumNode?.name) return enumNode;
|
|
@@ -787,19 +737,16 @@ function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, re
|
|
|
787
737
|
node,
|
|
788
738
|
...getEnumNames({
|
|
789
739
|
node,
|
|
790
|
-
|
|
791
|
-
enumTypeSuffix,
|
|
740
|
+
enum: enumOptions,
|
|
792
741
|
resolver
|
|
793
742
|
})
|
|
794
743
|
};
|
|
795
744
|
});
|
|
796
|
-
const shouldExportEnums =
|
|
797
|
-
const shouldExportType =
|
|
745
|
+
const shouldExportEnums = enumOptions.type !== "inlineLiteral";
|
|
746
|
+
const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
|
|
798
747
|
return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
|
|
799
748
|
node,
|
|
800
|
-
|
|
801
|
-
enumTypeSuffix,
|
|
802
|
-
enumKeyCasing,
|
|
749
|
+
enum: enumOptions,
|
|
803
750
|
resolver
|
|
804
751
|
}, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
|
|
805
752
|
name,
|
|
@@ -860,7 +807,7 @@ function resolveContentTypeVariants(entries, baseName) {
|
|
|
860
807
|
});
|
|
861
808
|
}
|
|
862
809
|
function getOperationParameters(node, options = {}) {
|
|
863
|
-
const params =
|
|
810
|
+
const params = caseParams(node.parameters, options.paramsCasing);
|
|
864
811
|
return {
|
|
865
812
|
path: params.filter((param) => param.in === "path"),
|
|
866
813
|
query: params.filter((param) => param.in === "query"),
|
|
@@ -875,26 +822,24 @@ function getOperationParameters(node, options = {}) {
|
|
|
875
822
|
* shared default naming so every plugin groups output consistently:
|
|
876
823
|
*
|
|
877
824
|
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
878
|
-
* - other groups use
|
|
825
|
+
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
879
826
|
*
|
|
880
827
|
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
881
828
|
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
882
829
|
* per-plugin convention.
|
|
883
830
|
*
|
|
884
831
|
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
885
|
-
* @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
|
|
886
832
|
*
|
|
887
833
|
* @example
|
|
888
834
|
* ```ts
|
|
889
|
-
* createGroupConfig(group
|
|
890
|
-
* createGroupConfig(group, { suffix: 'Requests' }) // plugin-cypress, plugin-mcp
|
|
835
|
+
* createGroupConfig(group) // shared across every plugin
|
|
891
836
|
* ```
|
|
892
837
|
*/
|
|
893
|
-
function createGroupConfig(group
|
|
838
|
+
function createGroupConfig(group) {
|
|
894
839
|
if (!group) return null;
|
|
895
840
|
const defaultName = (ctx) => {
|
|
896
841
|
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
897
|
-
return
|
|
842
|
+
return camelCase(ctx.group);
|
|
898
843
|
};
|
|
899
844
|
return {
|
|
900
845
|
...group,
|
|
@@ -910,8 +855,11 @@ function createGroupConfig(group, options) {
|
|
|
910
855
|
* Constraint metadata (min/max length, pattern, multipleOf, min/maxProperties) is emitted as plain-text lines.
|
|
911
856
|
|
|
912
857
|
*/
|
|
913
|
-
function
|
|
914
|
-
|
|
858
|
+
function isSchemaOptional(schema) {
|
|
859
|
+
return Boolean("optional" in schema && schema.optional || "nullish" in schema && schema.nullish);
|
|
860
|
+
}
|
|
861
|
+
function buildPropertyJSDocComments(schema, optional) {
|
|
862
|
+
const meta = syncSchemaRef(schema);
|
|
915
863
|
const isArray = meta?.primitive === "array";
|
|
916
864
|
const hasDescription = meta && "description" in meta && meta.description;
|
|
917
865
|
const formatComment = meta && "format" in meta && meta.format ? hasDescription ? [" ", `Format: \`${meta.format}\``] : ["@description", `Format: \`${meta.format}\``] : [];
|
|
@@ -924,83 +872,85 @@ function buildPropertyJSDocComments(schema) {
|
|
|
924
872
|
meta && "pattern" in meta && meta.pattern ? `@pattern ${meta.pattern}` : null,
|
|
925
873
|
meta && "default" in meta && meta.default !== void 0 ? `@default ${"primitive" in meta && meta.primitive === "string" ? stringify(meta.default) : meta.default}` : null,
|
|
926
874
|
meta && "example" in meta && meta.example !== void 0 ? `@example ${meta.example}` : null,
|
|
927
|
-
meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`,
|
|
875
|
+
meta && "primitive" in meta && meta.primitive ? [`@type ${meta.primitive}`, optional ?? isSchemaOptional(schema) ? " | undefined" : null].filter(Boolean).join("") : null
|
|
928
876
|
].filter(Boolean);
|
|
929
877
|
}
|
|
930
878
|
function buildParams(node, { params, resolver }) {
|
|
931
|
-
return ast.createSchema({
|
|
879
|
+
return ast.factory.createSchema({
|
|
932
880
|
type: "object",
|
|
933
|
-
properties: params.map((param) => ast.createProperty({
|
|
881
|
+
properties: params.map((param) => ast.factory.createProperty({
|
|
934
882
|
name: param.name,
|
|
935
883
|
required: param.required,
|
|
936
|
-
schema: ast.createSchema({
|
|
884
|
+
schema: ast.factory.createSchema({
|
|
937
885
|
type: "ref",
|
|
938
|
-
name: resolver.resolveParamName(node, param)
|
|
886
|
+
name: resolver.resolveParamName(node, param),
|
|
887
|
+
optional: !param.required
|
|
939
888
|
})
|
|
940
889
|
}))
|
|
941
890
|
});
|
|
942
891
|
}
|
|
943
892
|
function buildData(node, { resolver }) {
|
|
944
893
|
const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters(node);
|
|
945
|
-
return ast.createSchema({
|
|
894
|
+
return ast.factory.createSchema({
|
|
946
895
|
type: "object",
|
|
947
896
|
deprecated: node.deprecated,
|
|
948
897
|
properties: [
|
|
949
|
-
ast.createProperty({
|
|
898
|
+
ast.factory.createProperty({
|
|
950
899
|
name: "data",
|
|
951
|
-
schema: node.requestBody?.content?.[0]?.schema ? ast.createSchema({
|
|
900
|
+
schema: node.requestBody?.content?.[0]?.schema ? ast.factory.createSchema({
|
|
952
901
|
type: "ref",
|
|
953
902
|
name: resolver.resolveDataName(node),
|
|
954
903
|
optional: true
|
|
955
|
-
}) : ast.createSchema({
|
|
904
|
+
}) : ast.factory.createSchema({
|
|
956
905
|
type: "never",
|
|
957
906
|
primitive: void 0,
|
|
958
907
|
optional: true
|
|
959
908
|
})
|
|
960
909
|
}),
|
|
961
|
-
ast.createProperty({
|
|
910
|
+
ast.factory.createProperty({
|
|
962
911
|
name: "pathParams",
|
|
963
912
|
required: pathParams.length > 0,
|
|
964
913
|
schema: pathParams.length > 0 ? buildParams(node, {
|
|
965
914
|
params: pathParams,
|
|
966
915
|
resolver
|
|
967
|
-
}) : ast.createSchema({
|
|
916
|
+
}) : ast.factory.createSchema({
|
|
968
917
|
type: "never",
|
|
969
|
-
primitive: void 0
|
|
918
|
+
primitive: void 0,
|
|
919
|
+
optional: true
|
|
970
920
|
})
|
|
971
921
|
}),
|
|
972
|
-
ast.createProperty({
|
|
922
|
+
ast.factory.createProperty({
|
|
973
923
|
name: "queryParams",
|
|
974
|
-
schema: queryParams.length > 0 ? ast.createSchema({
|
|
924
|
+
schema: queryParams.length > 0 ? ast.factory.createSchema({
|
|
975
925
|
...buildParams(node, {
|
|
976
926
|
params: queryParams,
|
|
977
927
|
resolver
|
|
978
928
|
}),
|
|
979
929
|
optional: true
|
|
980
|
-
}) : ast.createSchema({
|
|
930
|
+
}) : ast.factory.createSchema({
|
|
981
931
|
type: "never",
|
|
982
932
|
primitive: void 0,
|
|
983
933
|
optional: true
|
|
984
934
|
})
|
|
985
935
|
}),
|
|
986
|
-
ast.createProperty({
|
|
936
|
+
ast.factory.createProperty({
|
|
987
937
|
name: "headerParams",
|
|
988
|
-
schema: headerParams.length > 0 ? ast.createSchema({
|
|
938
|
+
schema: headerParams.length > 0 ? ast.factory.createSchema({
|
|
989
939
|
...buildParams(node, {
|
|
990
940
|
params: headerParams,
|
|
991
941
|
resolver
|
|
992
942
|
}),
|
|
993
943
|
optional: true
|
|
994
|
-
}) : ast.createSchema({
|
|
944
|
+
}) : ast.factory.createSchema({
|
|
995
945
|
type: "never",
|
|
996
946
|
primitive: void 0,
|
|
997
947
|
optional: true
|
|
998
948
|
})
|
|
999
949
|
}),
|
|
1000
|
-
ast.createProperty({
|
|
950
|
+
ast.factory.createProperty({
|
|
1001
951
|
name: "url",
|
|
1002
952
|
required: true,
|
|
1003
|
-
schema: ast.createSchema({
|
|
953
|
+
schema: ast.factory.createSchema({
|
|
1004
954
|
type: "url",
|
|
1005
955
|
path: node.path
|
|
1006
956
|
})
|
|
@@ -1010,12 +960,12 @@ function buildData(node, { resolver }) {
|
|
|
1010
960
|
}
|
|
1011
961
|
function buildResponses(node, { resolver }) {
|
|
1012
962
|
if (node.responses.length === 0) return null;
|
|
1013
|
-
return ast.createSchema({
|
|
963
|
+
return ast.factory.createSchema({
|
|
1014
964
|
type: "object",
|
|
1015
|
-
properties: node.responses.map((res) => ast.createProperty({
|
|
965
|
+
properties: node.responses.map((res) => ast.factory.createProperty({
|
|
1016
966
|
name: String(res.statusCode),
|
|
1017
967
|
required: true,
|
|
1018
|
-
schema: ast.createSchema({
|
|
968
|
+
schema: ast.factory.createSchema({
|
|
1019
969
|
type: "ref",
|
|
1020
970
|
name: resolver.resolveResponseStatusName(node, res.statusCode)
|
|
1021
971
|
})
|
|
@@ -1025,9 +975,9 @@ function buildResponses(node, { resolver }) {
|
|
|
1025
975
|
function buildResponseUnion(node, { resolver }) {
|
|
1026
976
|
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
1027
977
|
if (responsesWithSchema.length === 0) return null;
|
|
1028
|
-
return ast.createSchema({
|
|
978
|
+
return ast.factory.createSchema({
|
|
1029
979
|
type: "union",
|
|
1030
|
-
members: responsesWithSchema.map((res) => ast.createSchema({
|
|
980
|
+
members: responsesWithSchema.map((res) => ast.factory.createSchema({
|
|
1031
981
|
type: "ref",
|
|
1032
982
|
name: resolver.resolveResponseStatusName(node, res.statusCode)
|
|
1033
983
|
}))
|
|
@@ -1051,13 +1001,13 @@ function isNonNullable(value) {
|
|
|
1051
1001
|
*
|
|
1052
1002
|
* @example Raw type node (no `typeName`)
|
|
1053
1003
|
* ```ts
|
|
1054
|
-
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array',
|
|
1004
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
|
|
1055
1005
|
* const typeNode = printer.print(schemaNode) // ts.TypeNode
|
|
1056
1006
|
* ```
|
|
1057
1007
|
*
|
|
1058
1008
|
* @example Full declaration (with `typeName`)
|
|
1059
1009
|
* ```ts
|
|
1060
|
-
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array',
|
|
1010
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
|
|
1061
1011
|
* const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
|
|
1062
1012
|
* ```
|
|
1063
1013
|
*/
|
|
@@ -1091,27 +1041,27 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
1091
1041
|
time: dateOrStringNode,
|
|
1092
1042
|
ref(node) {
|
|
1093
1043
|
if (!node.name) return null;
|
|
1094
|
-
const refName = node.ref ?
|
|
1095
|
-
return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.
|
|
1044
|
+
const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
|
|
1045
|
+
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);
|
|
1096
1046
|
},
|
|
1097
1047
|
enum(node) {
|
|
1098
1048
|
const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
|
|
1099
|
-
if (this.options.
|
|
1049
|
+
if (this.options.enum.type === "inlineLiteral" || !node.name) return createUnionDeclaration({
|
|
1100
1050
|
withParentheses: true,
|
|
1101
1051
|
nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
|
|
1102
1052
|
}) ?? void 0;
|
|
1103
|
-
return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.
|
|
1053
|
+
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);
|
|
1104
1054
|
},
|
|
1105
1055
|
union(node) {
|
|
1106
1056
|
const members = node.members ?? [];
|
|
1107
1057
|
const hasStringLiteral = members.some((m) => {
|
|
1108
1058
|
return ast.narrowSchema(m, ast.schemaTypes.enum)?.primitive === "string";
|
|
1109
1059
|
});
|
|
1110
|
-
const hasPlainString = members.some((m) =>
|
|
1060
|
+
const hasPlainString = members.some((m) => isStringType(m));
|
|
1111
1061
|
if (hasStringLiteral && hasPlainString) return createUnionDeclaration({
|
|
1112
1062
|
withParentheses: true,
|
|
1113
1063
|
nodes: members.map((m) => {
|
|
1114
|
-
if (
|
|
1064
|
+
if (isStringType(m)) return createIntersectionDeclaration({
|
|
1115
1065
|
nodes: [keywordTypeNodes.string, createTypeLiteralNode([])],
|
|
1116
1066
|
withParentheses: true
|
|
1117
1067
|
});
|
|
@@ -1131,7 +1081,7 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
1131
1081
|
},
|
|
1132
1082
|
array(node) {
|
|
1133
1083
|
return createArrayDeclaration({
|
|
1134
|
-
nodes: (node
|
|
1084
|
+
nodes: mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(isNonNullable),
|
|
1135
1085
|
arrayType: this.options.arrayType
|
|
1136
1086
|
}) ?? null;
|
|
1137
1087
|
},
|
|
@@ -1141,18 +1091,19 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
1141
1091
|
object(node) {
|
|
1142
1092
|
const { transform, options } = this;
|
|
1143
1093
|
const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType);
|
|
1144
|
-
const propertyNodes = node.
|
|
1145
|
-
const baseType =
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1094
|
+
const propertyNodes = mapSchemaProperties(node, (schema) => transform(schema)).map(({ name, property, output }) => {
|
|
1095
|
+
const baseType = output ?? keywordTypeNodes.unknown;
|
|
1096
|
+
const optional = !property.required || !!property.schema.optional || !!property.schema.nullish;
|
|
1097
|
+
const type = buildPropertyType(property.schema, baseType, options.optionalType, optional);
|
|
1098
|
+
const propMeta = syncSchemaRef(property.schema);
|
|
1148
1099
|
return appendJSDocToNode({
|
|
1149
1100
|
node: createPropertySignature({
|
|
1150
|
-
questionToken:
|
|
1151
|
-
name
|
|
1101
|
+
questionToken: optional ? addsQuestionToken : false,
|
|
1102
|
+
name,
|
|
1152
1103
|
type,
|
|
1153
1104
|
readOnly: propMeta?.readOnly
|
|
1154
1105
|
}),
|
|
1155
|
-
comments: buildPropertyJSDocComments(
|
|
1106
|
+
comments: buildPropertyJSDocComments(property.schema, optional)
|
|
1156
1107
|
});
|
|
1157
1108
|
});
|
|
1158
1109
|
const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, transform)];
|
|
@@ -1165,7 +1116,7 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
1165
1116
|
const { name, syntaxType = "type", description, keysToOmit } = this.options;
|
|
1166
1117
|
const transformed = this.transform(node);
|
|
1167
1118
|
if (!transformed) return null;
|
|
1168
|
-
const meta =
|
|
1119
|
+
const meta = syncSchemaRef(node);
|
|
1169
1120
|
if (!name) {
|
|
1170
1121
|
const withNullable = meta.nullable ? createUnionDeclaration({ nodes: [transformed, keywordTypeNodes.null] }) : transformed;
|
|
1171
1122
|
const result = (meta.nullish || meta.optional) && addsUndefined ? createUnionDeclaration({ nodes: [withNullable, keywordTypeNodes.undefined] }) : withNullable;
|
|
@@ -1204,15 +1155,14 @@ const printerTs = ast.definePrinter((options) => {
|
|
|
1204
1155
|
*/
|
|
1205
1156
|
const typeGenerator = defineGenerator({
|
|
1206
1157
|
name: "typescript",
|
|
1207
|
-
renderer:
|
|
1158
|
+
renderer: jsxRenderer,
|
|
1208
1159
|
schema(node, ctx) {
|
|
1209
|
-
const {
|
|
1160
|
+
const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options;
|
|
1210
1161
|
const { adapter, config, resolver, root } = ctx;
|
|
1211
1162
|
if (!node.name) return;
|
|
1212
|
-
const mode = ctx.getMode(output);
|
|
1213
1163
|
const enumSchemaNames = new Set(ctx.meta.enumNames);
|
|
1214
1164
|
function resolveImportName(schemaName) {
|
|
1215
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
1165
|
+
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
|
|
1216
1166
|
return resolver.resolveTypeName(schemaName);
|
|
1217
1167
|
}
|
|
1218
1168
|
const imports = adapter.getImports(node, (schemaName) => ({
|
|
@@ -1228,7 +1178,7 @@ const typeGenerator = defineGenerator({
|
|
|
1228
1178
|
}));
|
|
1229
1179
|
const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
|
|
1230
1180
|
const meta = {
|
|
1231
|
-
name: ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
1181
|
+
name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolver.resolveTypeName(node.name),
|
|
1232
1182
|
file: resolver.resolveFile({
|
|
1233
1183
|
name: node.name,
|
|
1234
1184
|
extname: ".ts"
|
|
@@ -1241,8 +1191,7 @@ const typeGenerator = defineGenerator({
|
|
|
1241
1191
|
const schemaPrinter = printerTs({
|
|
1242
1192
|
optionalType,
|
|
1243
1193
|
arrayType,
|
|
1244
|
-
|
|
1245
|
-
enumTypeSuffix,
|
|
1194
|
+
enum: enumOptions,
|
|
1246
1195
|
name: meta.name,
|
|
1247
1196
|
syntaxType,
|
|
1248
1197
|
description: node.description,
|
|
@@ -1270,7 +1219,7 @@ const typeGenerator = defineGenerator({
|
|
|
1270
1219
|
baseName: meta.file.baseName
|
|
1271
1220
|
}
|
|
1272
1221
|
}),
|
|
1273
|
-
children: [
|
|
1222
|
+
children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1274
1223
|
root: meta.file.path,
|
|
1275
1224
|
path: imp.path,
|
|
1276
1225
|
name: imp.name,
|
|
@@ -1282,19 +1231,16 @@ const typeGenerator = defineGenerator({
|
|
|
1282
1231
|
].join("-"))), /* @__PURE__ */ jsx(Type, {
|
|
1283
1232
|
name: meta.name,
|
|
1284
1233
|
node,
|
|
1285
|
-
|
|
1286
|
-
enumTypeSuffix,
|
|
1287
|
-
enumKeyCasing,
|
|
1234
|
+
enum: enumOptions,
|
|
1288
1235
|
resolver,
|
|
1289
1236
|
printer: schemaPrinter
|
|
1290
1237
|
})]
|
|
1291
1238
|
});
|
|
1292
1239
|
},
|
|
1293
1240
|
operation(node, ctx) {
|
|
1294
|
-
const {
|
|
1241
|
+
const { enum: enumOptions, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options;
|
|
1295
1242
|
const { adapter, config, resolver, root } = ctx;
|
|
1296
|
-
const
|
|
1297
|
-
const params = ast.caseParams(node.parameters, paramsCasing);
|
|
1243
|
+
const params = caseParams(node.parameters, paramsCasing);
|
|
1298
1244
|
const meta = { file: resolver.resolveFile({
|
|
1299
1245
|
name: node.operationId,
|
|
1300
1246
|
extname: ".ts",
|
|
@@ -1307,7 +1253,7 @@ const typeGenerator = defineGenerator({
|
|
|
1307
1253
|
}) };
|
|
1308
1254
|
const enumSchemaNames = new Set(ctx.meta.enumNames);
|
|
1309
1255
|
function resolveImportName(schemaName) {
|
|
1310
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
1256
|
+
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix);
|
|
1311
1257
|
return resolver.resolveTypeName(schemaName);
|
|
1312
1258
|
}
|
|
1313
1259
|
function renderSchemaType({ schema, name, keysToOmit }) {
|
|
@@ -1326,8 +1272,7 @@ const typeGenerator = defineGenerator({
|
|
|
1326
1272
|
const schemaPrinter = printerTs({
|
|
1327
1273
|
optionalType,
|
|
1328
1274
|
arrayType,
|
|
1329
|
-
|
|
1330
|
-
enumTypeSuffix,
|
|
1275
|
+
enum: enumOptions,
|
|
1331
1276
|
name,
|
|
1332
1277
|
syntaxType,
|
|
1333
1278
|
description: schema.description,
|
|
@@ -1336,7 +1281,7 @@ const typeGenerator = defineGenerator({
|
|
|
1336
1281
|
enumSchemaNames,
|
|
1337
1282
|
nodes: printer?.nodes
|
|
1338
1283
|
});
|
|
1339
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1284
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1340
1285
|
root: meta.file.path,
|
|
1341
1286
|
path: imp.path,
|
|
1342
1287
|
name: imp.name,
|
|
@@ -1348,9 +1293,7 @@ const typeGenerator = defineGenerator({
|
|
|
1348
1293
|
].join("-"))), /* @__PURE__ */ jsx(Type, {
|
|
1349
1294
|
name,
|
|
1350
1295
|
node: schema,
|
|
1351
|
-
|
|
1352
|
-
enumTypeSuffix,
|
|
1353
|
-
enumKeyCasing,
|
|
1296
|
+
enum: enumOptions,
|
|
1354
1297
|
resolver,
|
|
1355
1298
|
printer: schemaPrinter
|
|
1356
1299
|
})] });
|
|
@@ -1361,9 +1304,9 @@ const typeGenerator = defineGenerator({
|
|
|
1361
1304
|
*/
|
|
1362
1305
|
function buildContentTypeVariants(entries, baseName, decorate) {
|
|
1363
1306
|
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1364
|
-
const unionSchema = ast.createSchema({
|
|
1307
|
+
const unionSchema = ast.factory.createSchema({
|
|
1365
1308
|
type: "union",
|
|
1366
|
-
members: variants.map((variant) => ast.createSchema({
|
|
1309
|
+
members: variants.map((variant) => ast.factory.createSchema({
|
|
1367
1310
|
type: "ref",
|
|
1368
1311
|
name: variant.name
|
|
1369
1312
|
}))
|
|
@@ -1378,7 +1321,10 @@ const typeGenerator = defineGenerator({
|
|
|
1378
1321
|
})] });
|
|
1379
1322
|
}
|
|
1380
1323
|
const paramTypes = params.map((param) => renderSchemaType({
|
|
1381
|
-
schema:
|
|
1324
|
+
schema: {
|
|
1325
|
+
...param.schema,
|
|
1326
|
+
optional: !param.required
|
|
1327
|
+
},
|
|
1382
1328
|
name: resolver.resolveParamName(node, param)
|
|
1383
1329
|
}));
|
|
1384
1330
|
const requestBodyContent = node.requestBody?.content ?? [];
|
|
@@ -1481,7 +1427,7 @@ const typeGenerator = defineGenerator({
|
|
|
1481
1427
|
* casing/file-layout rules.
|
|
1482
1428
|
*
|
|
1483
1429
|
* The `default` method is supplied by `defineResolver`. It uses PascalCase for
|
|
1484
|
-
* type names and PascalCase
|
|
1430
|
+
* type names and PascalCase file paths (dotted names become `/`-joined) for files.
|
|
1485
1431
|
*
|
|
1486
1432
|
* @example Resolve a type and file name
|
|
1487
1433
|
* ```ts
|
|
@@ -1497,15 +1443,15 @@ const resolverTs = defineResolver(() => {
|
|
|
1497
1443
|
name: "default",
|
|
1498
1444
|
pluginName: "plugin-ts",
|
|
1499
1445
|
default(name, type) {
|
|
1500
|
-
|
|
1501
|
-
return
|
|
1446
|
+
if (type === "file") return toFilePath(name, pascalCase);
|
|
1447
|
+
return ensureValidVarName(pascalCase(name));
|
|
1502
1448
|
},
|
|
1503
1449
|
resolveTypeName(name) {
|
|
1504
1450
|
return ensureValidVarName(pascalCase(name));
|
|
1505
1451
|
},
|
|
1506
1452
|
resolvePathName(name, type) {
|
|
1507
|
-
|
|
1508
|
-
return
|
|
1453
|
+
if (type === "file") return toFilePath(name, pascalCase);
|
|
1454
|
+
return ensureValidVarName(pascalCase(name));
|
|
1509
1455
|
},
|
|
1510
1456
|
resolveParamName(node, param) {
|
|
1511
1457
|
return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`);
|
|
@@ -1563,7 +1509,7 @@ const pluginTsName = "plugin-ts";
|
|
|
1563
1509
|
* plugins: [
|
|
1564
1510
|
* pluginTs({
|
|
1565
1511
|
* output: { path: './types' },
|
|
1566
|
-
*
|
|
1512
|
+
* enum: { type: 'asConst' },
|
|
1567
1513
|
* optionalType: 'questionTokenAndUndefined',
|
|
1568
1514
|
* }),
|
|
1569
1515
|
* ],
|
|
@@ -1573,9 +1519,15 @@ const pluginTsName = "plugin-ts";
|
|
|
1573
1519
|
const pluginTs = definePlugin((options) => {
|
|
1574
1520
|
const { output = {
|
|
1575
1521
|
path: "types",
|
|
1576
|
-
|
|
1577
|
-
}, group, exclude = [], include, override = [],
|
|
1578
|
-
const groupConfig = createGroupConfig(group
|
|
1522
|
+
barrel: { type: "named" }
|
|
1523
|
+
}, group, exclude = [], include, override = [], enum: enumOptions = {}, optionalType = "questionToken", arrayType = "array", syntaxType = "type", paramsCasing, printer, resolver: userResolver, macros: userMacros, generators: userGenerators = [] } = options;
|
|
1524
|
+
const groupConfig = createGroupConfig(group);
|
|
1525
|
+
const resolvedEnum = {
|
|
1526
|
+
type: enumOptions.type ?? "asConst",
|
|
1527
|
+
constCasing: enumOptions.constCasing ?? "camelCase",
|
|
1528
|
+
typeSuffix: enumOptions.typeSuffix ?? "Key",
|
|
1529
|
+
keyCasing: enumOptions.keyCasing ?? "none"
|
|
1530
|
+
};
|
|
1579
1531
|
return {
|
|
1580
1532
|
name: pluginTsName,
|
|
1581
1533
|
options,
|
|
@@ -1588,9 +1540,7 @@ const pluginTs = definePlugin((options) => {
|
|
|
1588
1540
|
optionalType,
|
|
1589
1541
|
group: groupConfig,
|
|
1590
1542
|
arrayType,
|
|
1591
|
-
|
|
1592
|
-
enumTypeSuffix,
|
|
1593
|
-
enumKeyCasing,
|
|
1543
|
+
enum: resolvedEnum,
|
|
1594
1544
|
syntaxType,
|
|
1595
1545
|
paramsCasing,
|
|
1596
1546
|
printer
|
|
@@ -1599,7 +1549,7 @@ const pluginTs = definePlugin((options) => {
|
|
|
1599
1549
|
...resolverTs,
|
|
1600
1550
|
...userResolver
|
|
1601
1551
|
} : resolverTs);
|
|
1602
|
-
if (
|
|
1552
|
+
if (userMacros?.length) ctx.setMacros(userMacros);
|
|
1603
1553
|
ctx.addGenerator(typeGenerator);
|
|
1604
1554
|
for (const gen of userGenerators) ctx.addGenerator(gen);
|
|
1605
1555
|
} }
|
|
@@ -1607,37 +1557,75 @@ const pluginTs = definePlugin((options) => {
|
|
|
1607
1557
|
});
|
|
1608
1558
|
//#endregion
|
|
1609
1559
|
//#region src/printers/functionPrinter.ts
|
|
1610
|
-
const kindToHandlerKey = {
|
|
1611
|
-
FunctionParameter: "functionParameter",
|
|
1612
|
-
ParameterGroup: "parameterGroup",
|
|
1613
|
-
FunctionParameters: "functionParameters",
|
|
1614
|
-
ParamsType: "paramsType"
|
|
1615
|
-
};
|
|
1616
1560
|
/**
|
|
1617
|
-
*
|
|
1561
|
+
* Renders a {@link ast.TypeExpression} to its TypeScript source.
|
|
1562
|
+
*
|
|
1563
|
+
* - a `string` is a type reference, returned as-is
|
|
1564
|
+
* - an `IndexedAccessType` becomes `objectType['indexType']`
|
|
1565
|
+
* - a `TypeLiteral` becomes `{ key: Type; key?: Type }`
|
|
1618
1566
|
*
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1567
|
+
* `transformType` is applied once to the fully rendered type, matching how the
|
|
1568
|
+
* printer wrapped reference types before the `ts.factory` model.
|
|
1621
1569
|
*/
|
|
1622
|
-
|
|
1570
|
+
function renderType(type, transformType) {
|
|
1571
|
+
const rendered = renderTypeExpression(type);
|
|
1572
|
+
return transformType ? transformType(rendered) : rendered;
|
|
1573
|
+
}
|
|
1574
|
+
function renderTypeExpression(type) {
|
|
1575
|
+
if (typeof type === "string") return type;
|
|
1576
|
+
if (ast.indexedAccessTypeDef.is(type)) return `${type.target}['${type.key}']`;
|
|
1577
|
+
return `{ ${type.members.map((member) => {
|
|
1578
|
+
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(member.name) ? member.name : JSON.stringify(member.name);
|
|
1579
|
+
const value = renderTypeExpression(member.type);
|
|
1580
|
+
return member.optional ? `${key}?: ${value}` : `${key}: ${value}`;
|
|
1581
|
+
}).join("; ")} }`;
|
|
1582
|
+
}
|
|
1583
|
+
function groupMembers(param) {
|
|
1584
|
+
if (typeof param.name === "string") return null;
|
|
1585
|
+
return param.type && ast.typeLiteralDef.is(param.type) ? param.type.members : [];
|
|
1586
|
+
}
|
|
1623
1587
|
function rank(param) {
|
|
1624
|
-
if (param.kind === "ParameterGroup") {
|
|
1625
|
-
if (param.default) return PARAM_RANK.withDefault;
|
|
1626
|
-
return param.optional ?? param.properties.every((p) => p.optional || p.default !== void 0) ? PARAM_RANK.optional : PARAM_RANK.required;
|
|
1627
|
-
}
|
|
1628
1588
|
if (param.rest) return PARAM_RANK.rest;
|
|
1629
1589
|
if (param.default) return PARAM_RANK.withDefault;
|
|
1590
|
+
const members = groupMembers(param);
|
|
1591
|
+
if (members) return members.every((m) => m.optional) ? PARAM_RANK.optional : PARAM_RANK.required;
|
|
1630
1592
|
return param.optional ? PARAM_RANK.optional : PARAM_RANK.required;
|
|
1631
1593
|
}
|
|
1632
1594
|
function sortParams(params) {
|
|
1633
1595
|
return params.toSorted((a, b) => rank(a) - rank(b));
|
|
1634
1596
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1597
|
+
/**
|
|
1598
|
+
* Orders a destructured group's binding elements and type members together,
|
|
1599
|
+
* required fields first, matching how grouped children were sorted before.
|
|
1600
|
+
*/
|
|
1601
|
+
function sortedGroupMembers(name, type) {
|
|
1602
|
+
const members = type && ast.typeLiteralDef.is(type) ? type.members : [];
|
|
1603
|
+
const memberRank = (optional) => optional ? PARAM_RANK.optional : PARAM_RANK.required;
|
|
1604
|
+
return name.elements.map((element, index) => ({
|
|
1605
|
+
name: element.name,
|
|
1606
|
+
type: members[index]?.type,
|
|
1607
|
+
optional: members[index]?.optional
|
|
1608
|
+
})).toSorted((a, b) => memberRank(a.optional) - memberRank(b.optional));
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Renders the type annotation of a destructured group. An inline object type is
|
|
1612
|
+
* rendered from the already-sorted members so its key order matches the binding;
|
|
1613
|
+
* a reference type is rendered as-is.
|
|
1614
|
+
*/
|
|
1615
|
+
function renderGroupType(type, sorted, transformType) {
|
|
1616
|
+
if (!type) return void 0;
|
|
1617
|
+
if (!ast.typeLiteralDef.is(type)) return renderType(type);
|
|
1618
|
+
const typed = sorted.filter((member) => member.type !== void 0);
|
|
1619
|
+
if (!typed.length) return void 0;
|
|
1620
|
+
return `{ ${typed.map((member) => {
|
|
1621
|
+
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(member.name) ? member.name : JSON.stringify(member.name);
|
|
1622
|
+
const value = renderType(member.type, transformType);
|
|
1623
|
+
return member.optional ? `${key}?: ${value}` : `${key}: ${value}`;
|
|
1624
|
+
}).join("; ")} }`;
|
|
1637
1625
|
}
|
|
1638
1626
|
/**
|
|
1639
|
-
* Default function-signature printer.
|
|
1640
|
-
*
|
|
1627
|
+
* Default function-signature printer. Renders a parameter list in one of two modes:
|
|
1628
|
+
* `declaration` for the function signature and `call` for the call arguments.
|
|
1641
1629
|
*
|
|
1642
1630
|
* @example
|
|
1643
1631
|
* ```ts
|
|
@@ -1653,28 +1641,31 @@ function sortChildParams(params) {
|
|
|
1653
1641
|
* printer.print(sig) // → "petId: string, config: Config = {}"
|
|
1654
1642
|
* ```
|
|
1655
1643
|
*/
|
|
1656
|
-
const functionPrinter =
|
|
1644
|
+
const functionPrinter = ast.createPrinterFactory((node) => node.kind)((options) => ({
|
|
1657
1645
|
name: "functionParameters",
|
|
1658
1646
|
options,
|
|
1659
1647
|
nodes: {
|
|
1660
|
-
|
|
1661
|
-
if (node.kind !== "ParamsType") return null;
|
|
1662
|
-
if (node.variant === "member") return `${node.base}['${node.key}']`;
|
|
1663
|
-
if (node.variant === "struct") return `{ ${node.properties.map((p) => {
|
|
1664
|
-
const typeStr = this.transform(p.type);
|
|
1665
|
-
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name);
|
|
1666
|
-
return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`;
|
|
1667
|
-
}).join("; ")} }`;
|
|
1668
|
-
if (node.variant === "reference") return node.name;
|
|
1669
|
-
return null;
|
|
1670
|
-
},
|
|
1671
|
-
functionParameter(node) {
|
|
1648
|
+
FunctionParameter(node) {
|
|
1672
1649
|
const { mode, transformName, transformType } = this.options;
|
|
1650
|
+
const isGroup = typeof node.name !== "string";
|
|
1651
|
+
if (mode === "call") {
|
|
1652
|
+
if (isGroup) return `{ ${sortedGroupMembers(node.name, node.type).map((member) => member.name).join(", ")} }`;
|
|
1653
|
+
const name = transformName ? transformName(node.name) : node.name;
|
|
1654
|
+
return node.rest ? `...${name}` : name;
|
|
1655
|
+
}
|
|
1656
|
+
if (isGroup) {
|
|
1657
|
+
const sorted = sortedGroupMembers(node.name, node.type);
|
|
1658
|
+
const binding = `{ ${sorted.map((member) => transformName ? transformName(member.name) : member.name).join(", ")} }`;
|
|
1659
|
+
const allOptional = sorted.every((member) => member.optional);
|
|
1660
|
+
const type = renderGroupType(node.type, sorted, transformType);
|
|
1661
|
+
if (type) {
|
|
1662
|
+
if (allOptional) return `${binding}: ${type} = ${node.default ?? "{}"}`;
|
|
1663
|
+
return node.default ? `${binding}: ${type} = ${node.default}` : `${binding}: ${type}`;
|
|
1664
|
+
}
|
|
1665
|
+
return node.default ? `${binding} = ${node.default}` : binding;
|
|
1666
|
+
}
|
|
1673
1667
|
const name = transformName ? transformName(node.name) : node.name;
|
|
1674
|
-
const
|
|
1675
|
-
const type = rawType != null && transformType ? transformType(rawType) : rawType;
|
|
1676
|
-
if (mode === "keys" || mode === "values") return node.rest ? `...${name}` : name;
|
|
1677
|
-
if (mode === "call") return node.rest ? `...${name}` : name;
|
|
1668
|
+
const type = node.type ? renderType(node.type, transformType) : void 0;
|
|
1678
1669
|
if (node.rest) return type ? `...${name}: ${type}` : `...${name}`;
|
|
1679
1670
|
if (type) {
|
|
1680
1671
|
if (node.optional) return `${name}?: ${type}`;
|
|
@@ -1682,39 +1673,12 @@ const functionPrinter = defineFunctionPrinter((options) => ({
|
|
|
1682
1673
|
}
|
|
1683
1674
|
return node.default ? `${name} = ${node.default}` : name;
|
|
1684
1675
|
},
|
|
1685
|
-
|
|
1686
|
-
const { mode, transformName, transformType } = this.options;
|
|
1687
|
-
const sorted = sortChildParams(node.properties);
|
|
1688
|
-
const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== void 0);
|
|
1689
|
-
if (node.inline) return sorted.map((p) => this.transform(p)).filter(Boolean).join(", ");
|
|
1690
|
-
if (mode === "keys" || mode === "values") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
1691
|
-
if (mode === "call") return `{ ${sorted.map((p) => p.name).join(", ")} }`;
|
|
1692
|
-
const names = sorted.map((p) => {
|
|
1693
|
-
return transformName ? transformName(p.name) : p.name;
|
|
1694
|
-
});
|
|
1695
|
-
const nameStr = names.length ? `{ ${names.join(", ")} }` : void 0;
|
|
1696
|
-
if (!nameStr) return null;
|
|
1697
|
-
let typeAnnotation = node.type ? this.transform(node.type) ?? void 0 : void 0;
|
|
1698
|
-
if (!typeAnnotation) {
|
|
1699
|
-
const typeParts = sorted.filter((p) => p.type).map((p) => {
|
|
1700
|
-
const rawT = p.type ? this.transform(p.type) : void 0;
|
|
1701
|
-
const t = rawT != null && transformType ? transformType(rawT) : rawT;
|
|
1702
|
-
return p.optional || p.default !== void 0 ? `${p.name}?: ${t}` : `${p.name}: ${t}`;
|
|
1703
|
-
});
|
|
1704
|
-
typeAnnotation = typeParts.length ? `{ ${typeParts.join("; ")} }` : void 0;
|
|
1705
|
-
}
|
|
1706
|
-
if (typeAnnotation) {
|
|
1707
|
-
if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? "{}"}`;
|
|
1708
|
-
return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`;
|
|
1709
|
-
}
|
|
1710
|
-
return node.default ? `${nameStr} = ${node.default}` : nameStr;
|
|
1711
|
-
},
|
|
1712
|
-
functionParameters(node) {
|
|
1676
|
+
FunctionParameters(node) {
|
|
1713
1677
|
return sortParams(node.params).map((p) => this.transform(p)).filter(Boolean).join(", ");
|
|
1714
1678
|
}
|
|
1715
1679
|
}
|
|
1716
1680
|
}));
|
|
1717
1681
|
//#endregion
|
|
1718
|
-
export { Enum, Type, pluginTs as default, pluginTs, functionPrinter, pluginTsName, printerTs, resolverTs, typeGenerator };
|
|
1682
|
+
export { Enum, Type, pluginTs as default, pluginTs, functionPrinter, pluginTsName, printerTs, renderType, resolverTs, typeGenerator };
|
|
1719
1683
|
|
|
1720
1684
|
//# sourceMappingURL=index.js.map
|