@kubb/plugin-ts 5.0.0-beta.42 → 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/dist/index.cjs +106 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +94 -92
- package/dist/index.js +104 -163
- package/dist/index.js.map +1 -1
- package/package.json +8 -15
- package/src/components/Enum.tsx +30 -26
- package/src/components/Type.tsx +6 -11
- package/src/constants.ts +7 -7
- package/src/factory.ts +5 -38
- package/src/generators/typeGenerator.tsx +22 -42
- package/src/plugin.ts +12 -9
- package/src/printers/printerTs.ts +13 -24
- package/src/resolvers/resolverTs.ts +6 -6
- package/src/types.ts +77 -57
- package/src/utils.ts +1 -1
- package/extension.yaml +0 -1080
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-ts",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.56",
|
|
4
4
|
"description": "Generate TypeScript types, interfaces, and enums from your OpenAPI specification. The foundational plugin that powers type safety across the entire Kubb ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-generation",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"src",
|
|
26
26
|
"dist",
|
|
27
|
-
"extension.yaml",
|
|
28
27
|
"!/**/**.test.**",
|
|
29
28
|
"!/**/__tests__/**",
|
|
30
29
|
"!/**/__snapshots__/**"
|
|
@@ -46,9 +45,10 @@
|
|
|
46
45
|
"registry": "https://registry.npmjs.org/"
|
|
47
46
|
},
|
|
48
47
|
"dependencies": {
|
|
49
|
-
"@kubb/
|
|
50
|
-
"@kubb/
|
|
51
|
-
"@kubb/
|
|
48
|
+
"@kubb/ast": "5.0.0-beta.55",
|
|
49
|
+
"@kubb/core": "5.0.0-beta.55",
|
|
50
|
+
"@kubb/parser-ts": "5.0.0-beta.55",
|
|
51
|
+
"@kubb/renderer-jsx": "5.0.0-beta.55",
|
|
52
52
|
"typescript": "^6.0.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
@@ -56,21 +56,14 @@
|
|
|
56
56
|
"@internals/utils": "0.0.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
59
|
+
"@kubb/renderer-jsx": "5.0.0-beta.55"
|
|
60
60
|
},
|
|
61
|
-
"size-limit": [
|
|
62
|
-
{
|
|
63
|
-
"path": "./dist/*.js",
|
|
64
|
-
"limit": "510 KiB",
|
|
65
|
-
"gzip": true
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
61
|
"engines": {
|
|
69
62
|
"node": ">=22"
|
|
70
63
|
},
|
|
71
64
|
"scripts": {
|
|
72
|
-
"build": "tsdown
|
|
73
|
-
"clean": "
|
|
65
|
+
"build": "tsdown",
|
|
66
|
+
"clean": "node -e \"require('node:fs').rmSync('./dist', {recursive:true,force:true})\"",
|
|
74
67
|
"lint": "oxlint .",
|
|
75
68
|
"lint:fix": "oxlint --fix .",
|
|
76
69
|
"release": "pnpm publish --no-git-check",
|
package/src/components/Enum.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { camelCase
|
|
1
|
+
import { camelCase } from '@internals/utils'
|
|
2
|
+
import { trimQuotes } from '@kubb/ast/utils'
|
|
2
3
|
import type { ast } from '@kubb/core'
|
|
3
4
|
import { parserTs } from '@kubb/parser-ts'
|
|
4
5
|
import { File } from '@kubb/renderer-jsx'
|
|
@@ -7,11 +8,11 @@ import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_W
|
|
|
7
8
|
import * as factory from '../factory.ts'
|
|
8
9
|
import type { PluginTs, ResolverTs } from '../types.ts'
|
|
9
10
|
|
|
11
|
+
type EnumOptions = PluginTs['resolvedOptions']['enum']
|
|
12
|
+
|
|
10
13
|
type Props = {
|
|
11
14
|
node: ast.EnumSchemaNode
|
|
12
|
-
|
|
13
|
-
enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
|
|
14
|
-
enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
|
|
15
|
+
enum: EnumOptions
|
|
15
16
|
resolver: ResolverTs
|
|
16
17
|
key?: string | number | null
|
|
17
18
|
}
|
|
@@ -20,26 +21,19 @@ type Props = {
|
|
|
20
21
|
* Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
|
|
21
22
|
*
|
|
22
23
|
* The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
|
|
23
|
-
* valid TypeScript identifier. The resolver normalizes it
|
|
24
|
-
*
|
|
24
|
+
* valid TypeScript identifier. The resolver normalizes it. For inline enum properties the adapter
|
|
25
|
+
* already emits a PascalCase+suffix name, so resolution is typically a no-op.
|
|
26
|
+
*
|
|
27
|
+
* When `constCasing` is `'pascalCase'` and `typeSuffix` is empty, the const and the type
|
|
28
|
+
* resolve to the same name, which TypeScript merges into a single value+type declaration.
|
|
25
29
|
*/
|
|
26
|
-
export function getEnumNames({
|
|
27
|
-
node,
|
|
28
|
-
enumType,
|
|
29
|
-
enumTypeSuffix,
|
|
30
|
-
resolver,
|
|
31
|
-
}: {
|
|
32
|
-
node: ast.EnumSchemaNode
|
|
33
|
-
enumType: PluginTs['resolvedOptions']['enumType']
|
|
34
|
-
enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
|
|
35
|
-
resolver: ResolverTs
|
|
36
|
-
}): {
|
|
30
|
+
export function getEnumNames({ node, enum: enumOptions, resolver }: { node: ast.EnumSchemaNode; enum: EnumOptions; resolver: ResolverTs }): {
|
|
37
31
|
enumName: string
|
|
38
32
|
typeName: string
|
|
39
33
|
} {
|
|
40
34
|
const resolved = resolver.default(node.name!, 'type')
|
|
41
|
-
const enumName =
|
|
42
|
-
const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
35
|
+
const enumName = enumOptions.constCasing === 'pascalCase' ? resolved : camelCase(node.name!)
|
|
36
|
+
const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolved
|
|
43
37
|
|
|
44
38
|
return { enumName, typeName }
|
|
45
39
|
}
|
|
@@ -47,16 +41,16 @@ export function getEnumNames({
|
|
|
47
41
|
/**
|
|
48
42
|
* Renders the enum declaration(s) for a single named `EnumSchemaNode`.
|
|
49
43
|
*
|
|
50
|
-
* Depending on `
|
|
51
|
-
* - A runtime object (`asConst`
|
|
44
|
+
* Depending on `enum.type` this may emit:
|
|
45
|
+
* - A runtime object (`asConst`) plus a `typeof` type alias
|
|
52
46
|
* - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
|
|
53
47
|
* - A union literal type alias (`literal`)
|
|
54
48
|
*
|
|
55
49
|
* The emitted `File.Source` nodes carry the resolved names so that the barrel
|
|
56
50
|
* index picks up the correct export identifiers.
|
|
57
51
|
*/
|
|
58
|
-
export function Enum({ node,
|
|
59
|
-
const { enumName, typeName } = getEnumNames({ node,
|
|
52
|
+
export function Enum({ node, enum: enumOptions, resolver }: Props): KubbReactNode {
|
|
53
|
+
const { enumName, typeName } = getEnumNames({ node, enum: enumOptions, resolver })
|
|
60
54
|
|
|
61
55
|
const [nameNode, typeNode] = factory.createEnumDeclaration({
|
|
62
56
|
name: enumName,
|
|
@@ -64,10 +58,15 @@ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }
|
|
|
64
58
|
enums: (node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ??
|
|
65
59
|
node.enumValues?.filter((v): v is NonNullable<typeof v> => v !== null && v !== undefined).map((v) => [trimQuotes(v.toString()), v]) ??
|
|
66
60
|
[]) as Array<[string | number, string | number | boolean]>,
|
|
67
|
-
type:
|
|
68
|
-
enumKeyCasing,
|
|
61
|
+
type: enumOptions.type,
|
|
62
|
+
enumKeyCasing: enumOptions.keyCasing,
|
|
69
63
|
})
|
|
70
64
|
|
|
65
|
+
// When the const and the type share a name (pascalCase const + empty typeSuffix) they merge into
|
|
66
|
+
// one declaration. The const carries the barrel export, so keep the type alias in the file but out
|
|
67
|
+
// of the barrel to avoid re-exporting the name a second time as `export type { … }`.
|
|
68
|
+
const namesMerge = !!nameNode && enumName === typeName
|
|
69
|
+
|
|
71
70
|
return (
|
|
72
71
|
<>
|
|
73
72
|
{nameNode && (
|
|
@@ -75,7 +74,12 @@ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }
|
|
|
75
74
|
{parserTs.print(nameNode)}
|
|
76
75
|
</File.Source>
|
|
77
76
|
)}
|
|
78
|
-
<File.Source
|
|
77
|
+
<File.Source
|
|
78
|
+
name={typeName}
|
|
79
|
+
isIndexable={!namesMerge}
|
|
80
|
+
isExportable={!namesMerge && ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumOptions.type)}
|
|
81
|
+
isTypeOnly={ENUM_TYPES_WITH_TYPE_ONLY.has(enumOptions.type)}
|
|
82
|
+
>
|
|
79
83
|
{parserTs.print(typeNode)}
|
|
80
84
|
</File.Source>
|
|
81
85
|
</>
|
package/src/components/Type.tsx
CHANGED
|
@@ -13,13 +13,11 @@ type Props = {
|
|
|
13
13
|
* Created with `printerTs({ ..., nodes: options.printer?.nodes })`.
|
|
14
14
|
*/
|
|
15
15
|
printer: ast.Printer<PrinterTsFactory>
|
|
16
|
-
|
|
17
|
-
enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
|
|
18
|
-
enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
|
|
16
|
+
enum: PluginTs['resolvedOptions']['enum']
|
|
19
17
|
resolver: ResolverTs
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
export function Type({ name, node, printer,
|
|
20
|
+
export function Type({ name, node, printer, enum: enumOptions, resolver }: Props): KubbReactNode {
|
|
23
21
|
const enumSchemaNodes = ast.collect<ast.EnumSchemaNode>(node, {
|
|
24
22
|
schema(n): ast.EnumSchemaNode | undefined {
|
|
25
23
|
const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum)
|
|
@@ -36,20 +34,17 @@ export function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCas
|
|
|
36
34
|
const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
|
|
37
35
|
return {
|
|
38
36
|
node,
|
|
39
|
-
...getEnumNames({ node,
|
|
37
|
+
...getEnumNames({ node, enum: enumOptions, resolver }),
|
|
40
38
|
}
|
|
41
39
|
})
|
|
42
40
|
|
|
43
41
|
// Skip enum exports when using inlineLiteral
|
|
44
|
-
const shouldExportEnums =
|
|
45
|
-
const shouldExportType =
|
|
42
|
+
const shouldExportEnums = enumOptions.type !== 'inlineLiteral'
|
|
43
|
+
const shouldExportType = enumOptions.type === 'inlineLiteral' || enums.every((item) => item.typeName !== name)
|
|
46
44
|
|
|
47
45
|
return (
|
|
48
46
|
<>
|
|
49
|
-
{shouldExportEnums &&
|
|
50
|
-
enums.map(({ node }) => (
|
|
51
|
-
<Enum key={node.name} node={node} enumType={enumType} enumTypeSuffix={enumTypeSuffix} enumKeyCasing={enumKeyCasing} resolver={resolver} />
|
|
52
|
-
))}
|
|
47
|
+
{shouldExportEnums && enums.map(({ node }) => <Enum key={node.name} node={node} enum={enumOptions} resolver={resolver} />)}
|
|
53
48
|
{shouldExportType && (
|
|
54
49
|
<File.Source name={name} isTypeOnly isExportable isIndexable>
|
|
55
50
|
{output}
|
package/src/constants.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PluginTs } from './types.ts'
|
|
2
2
|
|
|
3
3
|
type OptionalType = PluginTs['resolvedOptions']['optionalType']
|
|
4
|
-
type EnumType = PluginTs['resolvedOptions']['
|
|
4
|
+
type EnumType = PluginTs['resolvedOptions']['enum']['type']
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* `optionalType` values that cause a property's type to include `| undefined`.
|
|
@@ -14,19 +14,19 @@ export const OPTIONAL_ADDS_UNDEFINED = new Set<OptionalType>(['undefined', 'ques
|
|
|
14
14
|
export const OPTIONAL_ADDS_QUESTION_TOKEN = new Set<OptionalType>(['questionToken', 'questionTokenAndUndefined'] as const)
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* `
|
|
17
|
+
* `enum.type` values that append a `typeSuffix` to the generated enum type alias.
|
|
18
18
|
*/
|
|
19
|
-
export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst'
|
|
19
|
+
export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst'] as const)
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* `
|
|
22
|
+
* `enum.type` values that require a runtime value declaration (object, enum, or literal).
|
|
23
23
|
*/
|
|
24
|
-
export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', '
|
|
24
|
+
export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'constEnum', 'literal', undefined] as const)
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* `
|
|
27
|
+
* `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
|
|
28
28
|
*/
|
|
29
|
-
export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', '
|
|
29
|
+
export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'literal', undefined] as const)
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Ordering priority for function parameters: lower = sorted earlier.
|
package/src/factory.ts
CHANGED
|
@@ -361,18 +361,6 @@ export function createTypeDeclaration({
|
|
|
361
361
|
})
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
/**
|
|
365
|
-
* Creates a TypeScript namespace declaration (exported module).
|
|
366
|
-
*/
|
|
367
|
-
export function createNamespaceDeclaration({ statements, name }: { name: string; statements: Array<ts.Statement> }) {
|
|
368
|
-
return factory.createModuleDeclaration(
|
|
369
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
370
|
-
factory.createIdentifier(name),
|
|
371
|
-
factory.createModuleBlock(statements),
|
|
372
|
-
ts.NodeFlags.Namespace,
|
|
373
|
-
)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
364
|
/**
|
|
377
365
|
* Creates an import declaration with support for default imports, named imports, namespace imports, and type-only imports.
|
|
378
366
|
* Optionally rename imported members with `propertyName` and `name` pairs.
|
|
@@ -528,15 +516,14 @@ export function createEnumDeclaration({
|
|
|
528
516
|
enumKeyCasing = 'none',
|
|
529
517
|
}: {
|
|
530
518
|
/**
|
|
531
|
-
* Choose to use `enum`, `asConst`, `
|
|
519
|
+
* Choose to use `enum`, `asConst`, `constEnum`, or `literal` for enums.
|
|
532
520
|
* - `enum`: TypeScript enum
|
|
533
|
-
* - `asConst`: const with
|
|
534
|
-
* - `asPascalConst`: const with PascalCase name (e.g., `PetType`)
|
|
521
|
+
* - `asConst`: const object asserted with `as const` (the caller decides the const name casing)
|
|
535
522
|
* - `constEnum`: const enum
|
|
536
523
|
* - `literal`: literal union type
|
|
537
524
|
* @default `'enum'`
|
|
538
525
|
*/
|
|
539
|
-
type?: 'enum' | 'asConst' | '
|
|
526
|
+
type?: 'enum' | 'asConst' | 'constEnum' | 'literal' | 'inlineLiteral'
|
|
540
527
|
/**
|
|
541
528
|
* Enum name in camelCase.
|
|
542
529
|
*/
|
|
@@ -629,8 +616,8 @@ export function createEnumDeclaration({
|
|
|
629
616
|
}
|
|
630
617
|
|
|
631
618
|
// used when using `as const` instead of an TypeScript enum.
|
|
632
|
-
// name is already
|
|
633
|
-
// typeName
|
|
619
|
+
// name is already cased by the caller (camelCase or pascalCase, driven by enum.constCasing).
|
|
620
|
+
// typeName carries the typeSuffix for the type alias, so we use name for the const identifier.
|
|
634
621
|
const identifierName = name
|
|
635
622
|
|
|
636
623
|
// When there are no enum items (empty or all-null enum), don't generate a runtime const.
|
|
@@ -816,21 +803,11 @@ export const createStringLiteral = factory.createStringLiteral
|
|
|
816
803
|
*/
|
|
817
804
|
export const createArrayTypeNode = factory.createArrayTypeNode
|
|
818
805
|
|
|
819
|
-
/**
|
|
820
|
-
* Creates a parenthesized type node to control operator precedence.
|
|
821
|
-
*/
|
|
822
|
-
export const createParenthesizedType = factory.createParenthesizedType
|
|
823
|
-
|
|
824
806
|
/**
|
|
825
807
|
* Creates a literal type node (e.g., `'hello'`, `42`, `true`).
|
|
826
808
|
*/
|
|
827
809
|
export const createLiteralTypeNode = factory.createLiteralTypeNode
|
|
828
810
|
|
|
829
|
-
/**
|
|
830
|
-
* Creates a null literal type node.
|
|
831
|
-
*/
|
|
832
|
-
export const createNull = factory.createNull
|
|
833
|
-
|
|
834
811
|
/**
|
|
835
812
|
* Creates an identifier node.
|
|
836
813
|
*/
|
|
@@ -861,16 +838,6 @@ export const createTrue = factory.createTrue
|
|
|
861
838
|
*/
|
|
862
839
|
export const createFalse = factory.createFalse
|
|
863
840
|
|
|
864
|
-
/**
|
|
865
|
-
* Creates an indexed access type node (e.g., `T[K]`).
|
|
866
|
-
*/
|
|
867
|
-
export const createIndexedAccessTypeNode = factory.createIndexedAccessTypeNode
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* Creates a type operator node (e.g., `keyof T`, `readonly T[]`).
|
|
871
|
-
*/
|
|
872
|
-
export const createTypeOperatorNode = factory.createTypeOperatorNode
|
|
873
|
-
|
|
874
841
|
/**
|
|
875
842
|
* Creates a prefix unary expression (e.g., negative numbers, logical not).
|
|
876
843
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveContentTypeVariants } from '@internals/shared'
|
|
2
2
|
import { ast, defineGenerator } from '@kubb/core'
|
|
3
|
-
import { File,
|
|
3
|
+
import { File, jsxRenderer } from '@kubb/renderer-jsx'
|
|
4
4
|
import { Type } from '../components/Type.tsx'
|
|
5
5
|
import { ENUM_TYPES_WITH_KEY_SUFFIX } from '../constants.ts'
|
|
6
6
|
import { printerTs } from '../printers/printerTs.ts'
|
|
@@ -15,22 +15,21 @@ import { buildData, buildResponses, buildResponseUnion } from '../utils.ts'
|
|
|
15
15
|
*/
|
|
16
16
|
export const typeGenerator = defineGenerator<PluginTs>({
|
|
17
17
|
name: 'typescript',
|
|
18
|
-
renderer:
|
|
18
|
+
renderer: jsxRenderer,
|
|
19
19
|
schema(node, ctx) {
|
|
20
|
-
const {
|
|
20
|
+
const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options
|
|
21
21
|
const { adapter, config, resolver, root } = ctx
|
|
22
22
|
|
|
23
23
|
if (!node.name) {
|
|
24
24
|
return
|
|
25
25
|
}
|
|
26
|
-
const mode = ctx.getMode(output)
|
|
27
26
|
// Build a set of schema names that are enums so the ref handler and getImports
|
|
28
27
|
// callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
|
|
29
28
|
const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
|
|
30
29
|
|
|
31
30
|
function resolveImportName(schemaName: string): string {
|
|
32
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
33
|
-
return resolver.resolveEnumKeyName({ name: schemaName },
|
|
31
|
+
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) {
|
|
32
|
+
return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix)
|
|
34
33
|
}
|
|
35
34
|
return resolver.resolveTypeName(schemaName)
|
|
36
35
|
}
|
|
@@ -43,15 +42,17 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
43
42
|
const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum)
|
|
44
43
|
|
|
45
44
|
const meta = {
|
|
46
|
-
name:
|
|
45
|
+
name:
|
|
46
|
+
ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema
|
|
47
|
+
? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix)
|
|
48
|
+
: resolver.resolveTypeName(node.name),
|
|
47
49
|
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
|
|
48
50
|
} as const
|
|
49
51
|
|
|
50
52
|
const schemaPrinter = printerTs({
|
|
51
53
|
optionalType,
|
|
52
54
|
arrayType,
|
|
53
|
-
|
|
54
|
-
enumTypeSuffix,
|
|
55
|
+
enum: enumOptions,
|
|
55
56
|
name: meta.name,
|
|
56
57
|
syntaxType,
|
|
57
58
|
description: node.description,
|
|
@@ -68,28 +69,17 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
68
69
|
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
69
70
|
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
70
71
|
>
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<Type
|
|
76
|
-
name={meta.name}
|
|
77
|
-
node={node}
|
|
78
|
-
enumType={enumType}
|
|
79
|
-
enumTypeSuffix={enumTypeSuffix}
|
|
80
|
-
enumKeyCasing={enumKeyCasing}
|
|
81
|
-
resolver={resolver}
|
|
82
|
-
printer={schemaPrinter}
|
|
83
|
-
/>
|
|
72
|
+
{imports.map((imp) => (
|
|
73
|
+
<File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
|
|
74
|
+
))}
|
|
75
|
+
<Type name={meta.name} node={node} enum={enumOptions} resolver={resolver} printer={schemaPrinter} />
|
|
84
76
|
</File>
|
|
85
77
|
)
|
|
86
78
|
},
|
|
87
79
|
operation(node, ctx) {
|
|
88
|
-
const {
|
|
80
|
+
const { enum: enumOptions, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options
|
|
89
81
|
const { adapter, config, resolver, root } = ctx
|
|
90
82
|
|
|
91
|
-
const mode = ctx.getMode(output)
|
|
92
|
-
|
|
93
83
|
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
94
84
|
|
|
95
85
|
const meta = {
|
|
@@ -104,8 +94,8 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
104
94
|
const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
|
|
105
95
|
|
|
106
96
|
function resolveImportName(schemaName: string): string {
|
|
107
|
-
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(
|
|
108
|
-
return resolver.resolveEnumKeyName({ name: schemaName },
|
|
97
|
+
if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) {
|
|
98
|
+
return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix)
|
|
109
99
|
}
|
|
110
100
|
return resolver.resolveTypeName(schemaName)
|
|
111
101
|
}
|
|
@@ -121,8 +111,7 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
121
111
|
const schemaPrinter = printerTs({
|
|
122
112
|
optionalType,
|
|
123
113
|
arrayType,
|
|
124
|
-
|
|
125
|
-
enumTypeSuffix,
|
|
114
|
+
enum: enumOptions,
|
|
126
115
|
name,
|
|
127
116
|
syntaxType,
|
|
128
117
|
description: schema.description,
|
|
@@ -134,19 +123,10 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
134
123
|
|
|
135
124
|
return (
|
|
136
125
|
<>
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<Type
|
|
142
|
-
name={name}
|
|
143
|
-
node={schema}
|
|
144
|
-
enumType={enumType}
|
|
145
|
-
enumTypeSuffix={enumTypeSuffix}
|
|
146
|
-
enumKeyCasing={enumKeyCasing}
|
|
147
|
-
resolver={resolver}
|
|
148
|
-
printer={schemaPrinter}
|
|
149
|
-
/>
|
|
126
|
+
{imports.map((imp) => (
|
|
127
|
+
<File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
|
|
128
|
+
))}
|
|
129
|
+
<Type name={name} node={schema} enum={enumOptions} resolver={resolver} printer={schemaPrinter} />
|
|
150
130
|
</>
|
|
151
131
|
)
|
|
152
132
|
}
|
package/src/plugin.ts
CHANGED
|
@@ -27,7 +27,7 @@ export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
|
|
|
27
27
|
* plugins: [
|
|
28
28
|
* pluginTs({
|
|
29
29
|
* output: { path: './types' },
|
|
30
|
-
*
|
|
30
|
+
* enum: { type: 'asConst' },
|
|
31
31
|
* optionalType: 'questionTokenAndUndefined',
|
|
32
32
|
* }),
|
|
33
33
|
* ],
|
|
@@ -36,14 +36,12 @@ export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
|
|
|
36
36
|
*/
|
|
37
37
|
export const pluginTs = definePlugin<PluginTs>((options) => {
|
|
38
38
|
const {
|
|
39
|
-
output = { path: 'types',
|
|
39
|
+
output = { path: 'types', barrel: { type: 'named' } },
|
|
40
40
|
group,
|
|
41
41
|
exclude = [],
|
|
42
42
|
include,
|
|
43
43
|
override = [],
|
|
44
|
-
|
|
45
|
-
enumTypeSuffix = 'Key',
|
|
46
|
-
enumKeyCasing = 'none',
|
|
44
|
+
enum: enumOptions = {},
|
|
47
45
|
optionalType = 'questionToken',
|
|
48
46
|
arrayType = 'array',
|
|
49
47
|
syntaxType = 'type',
|
|
@@ -54,7 +52,14 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
|
|
|
54
52
|
generators: userGenerators = [],
|
|
55
53
|
} = options
|
|
56
54
|
|
|
57
|
-
const groupConfig = createGroupConfig(group
|
|
55
|
+
const groupConfig = createGroupConfig(group)
|
|
56
|
+
|
|
57
|
+
const resolvedEnum = {
|
|
58
|
+
type: enumOptions.type ?? 'asConst',
|
|
59
|
+
constCasing: enumOptions.constCasing ?? 'camelCase',
|
|
60
|
+
typeSuffix: enumOptions.typeSuffix ?? 'Key',
|
|
61
|
+
keyCasing: enumOptions.keyCasing ?? 'none',
|
|
62
|
+
}
|
|
58
63
|
|
|
59
64
|
return {
|
|
60
65
|
name: pluginTsName,
|
|
@@ -69,9 +74,7 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
|
|
|
69
74
|
optionalType,
|
|
70
75
|
group: groupConfig,
|
|
71
76
|
arrayType,
|
|
72
|
-
|
|
73
|
-
enumTypeSuffix,
|
|
74
|
-
enumKeyCasing,
|
|
77
|
+
enum: resolvedEnum,
|
|
75
78
|
syntaxType,
|
|
76
79
|
paramsCasing,
|
|
77
80
|
printer,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extractRefName } from '@kubb/ast/utils'
|
|
1
2
|
import { ast } from '@kubb/core'
|
|
2
3
|
import { parserTs } from '@kubb/parser-ts'
|
|
3
4
|
import type ts from 'typescript'
|
|
@@ -50,23 +51,11 @@ export type PrinterTsOptions = {
|
|
|
50
51
|
*/
|
|
51
52
|
arrayType: PluginTs['resolvedOptions']['arrayType']
|
|
52
53
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* - `'asConst'` generates as const declarations
|
|
57
|
-
*
|
|
58
|
-
* @default `'inlineLiteral'`
|
|
59
|
-
*/
|
|
60
|
-
enumType: PluginTs['resolvedOptions']['enumType']
|
|
61
|
-
/**
|
|
62
|
-
* Suffix appended to enum key reference names.
|
|
63
|
-
*
|
|
64
|
-
* @example Enum key naming
|
|
65
|
-
* `StatusKey` when `enumType` is `'asConst'`
|
|
66
|
-
*
|
|
67
|
-
* @default `'Key'`
|
|
54
|
+
* Grouped enum settings. The printer emits references to enums, not the enum declarations, so only
|
|
55
|
+
* `type` (the output format) and `typeSuffix` (the enum key reference suffix) matter here.
|
|
56
|
+
* `constCasing` and `keyCasing` are ignored.
|
|
68
57
|
*/
|
|
69
|
-
|
|
58
|
+
enum: PluginTs['resolvedOptions']['enum']
|
|
70
59
|
/**
|
|
71
60
|
* Syntax for generated declarations.
|
|
72
61
|
* - `'type'` generates type aliases
|
|
@@ -124,13 +113,13 @@ type PrinterTs = PrinterTsFactory
|
|
|
124
113
|
*
|
|
125
114
|
* @example Raw type node (no `typeName`)
|
|
126
115
|
* ```ts
|
|
127
|
-
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array',
|
|
116
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' } })
|
|
128
117
|
* const typeNode = printer.print(schemaNode) // ts.TypeNode
|
|
129
118
|
* ```
|
|
130
119
|
*
|
|
131
120
|
* @example Full declaration (with `typeName`)
|
|
132
121
|
* ```ts
|
|
133
|
-
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array',
|
|
122
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enum: { type: 'inlineLiteral' }, typeName: 'MyType' })
|
|
134
123
|
* const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
|
|
135
124
|
* ```
|
|
136
125
|
*/
|
|
@@ -173,15 +162,15 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
|
|
|
173
162
|
// Use the canonical name from the $ref path — node.name may have been overridden
|
|
174
163
|
// (e.g. by single-member allOf flatten using the property-derived child name).
|
|
175
164
|
// Inline refs (without $ref) from utils already carry resolved type names.
|
|
176
|
-
const refName = node.ref ? (
|
|
165
|
+
const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
|
|
177
166
|
|
|
178
167
|
// When a Key suffix is configured, enum refs must use the suffixed name (e.g. `StatusKey`)
|
|
179
168
|
// so the reference matches what the enum file actually exports.
|
|
180
169
|
const isEnumRef =
|
|
181
|
-
node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.
|
|
170
|
+
node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName)
|
|
182
171
|
|
|
183
172
|
const name = isEnumRef
|
|
184
|
-
? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.
|
|
173
|
+
? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enum.typeSuffix)
|
|
185
174
|
: node.ref
|
|
186
175
|
? this.options.resolver.default(refName, 'type')
|
|
187
176
|
: refName
|
|
@@ -191,7 +180,7 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
|
|
|
191
180
|
enum(node) {
|
|
192
181
|
const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
|
|
193
182
|
|
|
194
|
-
if (this.options.
|
|
183
|
+
if (this.options.enum.type === 'inlineLiteral' || !node.name) {
|
|
195
184
|
const literalNodes = values
|
|
196
185
|
.filter((v): v is string | number | boolean => v !== null && v !== undefined)
|
|
197
186
|
.map((value) => factory.constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
|
|
@@ -201,8 +190,8 @@ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
|
|
|
201
190
|
}
|
|
202
191
|
|
|
203
192
|
const resolvedName =
|
|
204
|
-
ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.
|
|
205
|
-
? this.options.resolver.resolveEnumKeyName(node, this.options.
|
|
193
|
+
ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix
|
|
194
|
+
? this.options.resolver.resolveEnumKeyName(node, this.options.enum.typeSuffix)
|
|
206
195
|
: this.options.resolver.default(node.name, 'type')
|
|
207
196
|
|
|
208
197
|
return factory.createTypeReferenceNode(resolvedName, undefined)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ensureValidVarName, pascalCase } from '@internals/utils'
|
|
1
|
+
import { ensureValidVarName, pascalCase, toFilePath } from '@internals/utils'
|
|
2
2
|
import { defineResolver } from '@kubb/core'
|
|
3
3
|
import type { PluginTs } from '../types.ts'
|
|
4
4
|
|
|
@@ -9,7 +9,7 @@ import type { PluginTs } from '../types.ts'
|
|
|
9
9
|
* casing/file-layout rules.
|
|
10
10
|
*
|
|
11
11
|
* The `default` method is supplied by `defineResolver`. It uses PascalCase for
|
|
12
|
-
* type names and PascalCase
|
|
12
|
+
* type names and PascalCase file paths (dotted names become `/`-joined) for files.
|
|
13
13
|
*
|
|
14
14
|
* @example Resolve a type and file name
|
|
15
15
|
* ```ts
|
|
@@ -25,15 +25,15 @@ export const resolverTs = defineResolver<PluginTs>(() => {
|
|
|
25
25
|
name: 'default',
|
|
26
26
|
pluginName: 'plugin-ts',
|
|
27
27
|
default(name, type) {
|
|
28
|
-
|
|
29
|
-
return
|
|
28
|
+
if (type === 'file') return toFilePath(name, pascalCase)
|
|
29
|
+
return ensureValidVarName(pascalCase(name))
|
|
30
30
|
},
|
|
31
31
|
resolveTypeName(name) {
|
|
32
32
|
return ensureValidVarName(pascalCase(name))
|
|
33
33
|
},
|
|
34
34
|
resolvePathName(name, type) {
|
|
35
|
-
|
|
36
|
-
return
|
|
35
|
+
if (type === 'file') return toFilePath(name, pascalCase)
|
|
36
|
+
return ensureValidVarName(pascalCase(name))
|
|
37
37
|
},
|
|
38
38
|
resolveParamName(node, param) {
|
|
39
39
|
return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`)
|