@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/package.json
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-ts",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
4
|
-
"description": "TypeScript
|
|
3
|
+
"version": "5.0.0-beta.56",
|
|
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
|
-
"code-
|
|
6
|
+
"code-generation",
|
|
7
7
|
"codegen",
|
|
8
|
+
"enums",
|
|
8
9
|
"interfaces",
|
|
9
10
|
"kubb",
|
|
10
|
-
"oas",
|
|
11
11
|
"openapi",
|
|
12
|
-
"plugins",
|
|
13
12
|
"swagger",
|
|
14
|
-
"type-definitions",
|
|
15
13
|
"type-generation",
|
|
16
|
-
"type-safe",
|
|
17
14
|
"types",
|
|
18
15
|
"typescript"
|
|
19
16
|
],
|
|
@@ -27,7 +24,6 @@
|
|
|
27
24
|
"files": [
|
|
28
25
|
"src",
|
|
29
26
|
"dist",
|
|
30
|
-
"extension.yaml",
|
|
31
27
|
"!/**/**.test.**",
|
|
32
28
|
"!/**/__tests__/**",
|
|
33
29
|
"!/**/__snapshots__/**"
|
|
@@ -49,35 +45,30 @@
|
|
|
49
45
|
"registry": "https://registry.npmjs.org/"
|
|
50
46
|
},
|
|
51
47
|
"dependencies": {
|
|
52
|
-
"@kubb/
|
|
53
|
-
"@kubb/
|
|
54
|
-
"@kubb/
|
|
55
|
-
"
|
|
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",
|
|
56
52
|
"typescript": "^6.0.3"
|
|
57
53
|
},
|
|
58
54
|
"devDependencies": {
|
|
55
|
+
"@internals/shared": "0.0.0",
|
|
59
56
|
"@internals/utils": "0.0.0"
|
|
60
57
|
},
|
|
61
58
|
"peerDependencies": {
|
|
62
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
59
|
+
"@kubb/renderer-jsx": "5.0.0-beta.55"
|
|
63
60
|
},
|
|
64
|
-
"size-limit": [
|
|
65
|
-
{
|
|
66
|
-
"path": "./dist/*.js",
|
|
67
|
-
"limit": "510 KiB",
|
|
68
|
-
"gzip": true
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
61
|
"engines": {
|
|
72
62
|
"node": ">=22"
|
|
73
63
|
},
|
|
74
64
|
"scripts": {
|
|
75
|
-
"build": "tsdown
|
|
76
|
-
"clean": "
|
|
65
|
+
"build": "tsdown",
|
|
66
|
+
"clean": "node -e \"require('node:fs').rmSync('./dist', {recursive:true,force:true})\"",
|
|
77
67
|
"lint": "oxlint .",
|
|
78
68
|
"lint:fix": "oxlint --fix .",
|
|
79
69
|
"release": "pnpm publish --no-git-check",
|
|
80
70
|
"release:canary": "bash ../../.github/canary.sh && node ../../scripts/build.js canary && pnpm publish --no-git-check",
|
|
71
|
+
"release:stage": "pnpm stage publish --no-git-check",
|
|
81
72
|
"start": "tsdown --watch",
|
|
82
73
|
"test": "vitest --passWithNoTests",
|
|
83
74
|
"typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false"
|
package/src/components/Enum.tsx
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
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
|
-
import {
|
|
4
|
+
import { parserTs } from '@kubb/parser-ts'
|
|
4
5
|
import { File } from '@kubb/renderer-jsx'
|
|
5
6
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
6
7
|
import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_WITH_TYPE_ONLY } from '../constants.ts'
|
|
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,19 +58,29 @@ 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 && (
|
|
74
73
|
<File.Source name={enumName} isExportable isIndexable isTypeOnly={false}>
|
|
75
|
-
{
|
|
74
|
+
{parserTs.print(nameNode)}
|
|
76
75
|
</File.Source>
|
|
77
76
|
)}
|
|
78
|
-
<File.Source
|
|
79
|
-
{
|
|
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
|
+
>
|
|
83
|
+
{parserTs.print(typeNode)}
|
|
80
84
|
</File.Source>
|
|
81
85
|
</>
|
|
82
86
|
)
|
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
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { camelCase, pascalCase, screamingSnakeCase, snakeCase } from '@internals/utils'
|
|
2
2
|
import { ast } from '@kubb/core'
|
|
3
|
-
import { isNumber, sortBy } from 'remeda'
|
|
4
3
|
import ts from 'typescript'
|
|
5
4
|
import { OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
|
|
6
5
|
|
|
7
6
|
const { SyntaxKind, factory } = ts
|
|
8
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Compares two strings by UTF-16 code unit, keeping sorted output identical across platforms
|
|
10
|
+
* regardless of locale.
|
|
11
|
+
*/
|
|
12
|
+
function compareStrings(a: string, b: string): number {
|
|
13
|
+
if (a < b) return -1
|
|
14
|
+
if (a > b) return 1
|
|
15
|
+
return 0
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isNumber(value: unknown): value is number {
|
|
19
|
+
return typeof value === 'number' && !Number.isNaN(value)
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
// https://ts-ast-viewer.com/
|
|
10
23
|
|
|
11
24
|
/**
|
|
@@ -27,13 +40,30 @@ export const syntaxKind = {
|
|
|
27
40
|
stringLiteral: SyntaxKind.StringLiteral,
|
|
28
41
|
} as const
|
|
29
42
|
|
|
43
|
+
function isNonNullable<T>(value: T | null | undefined): value is T {
|
|
44
|
+
return value !== null && value !== undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
function isValidIdentifier(str: string): boolean {
|
|
31
48
|
if (!str.length || str.trim() !== str) {
|
|
32
49
|
return false
|
|
33
50
|
}
|
|
34
|
-
const node = ts.parseIsolatedEntityName(str, ts.ScriptTarget.Latest)
|
|
35
51
|
|
|
36
|
-
|
|
52
|
+
// Mirrors `ts.isIdentifierText`, which is not in the public type declarations.
|
|
53
|
+
// Walking by code point with `isIdentifierStart`/`isIdentifierPart` rejects
|
|
54
|
+
// invalid names such as private identifiers (`#FOO`), forcing `propertyName`
|
|
55
|
+
// to quote them.
|
|
56
|
+
let ch = str.codePointAt(0)!
|
|
57
|
+
if (!ts.isIdentifierStart(ch, ts.ScriptTarget.Latest)) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
for (let i = ch > 0xffff ? 2 : 1; i < str.length; i += ch > 0xffff ? 2 : 1) {
|
|
61
|
+
ch = str.codePointAt(i)!
|
|
62
|
+
if (!ts.isIdentifierPart(ch, ts.ScriptTarget.Latest)) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return true
|
|
37
67
|
}
|
|
38
68
|
|
|
39
69
|
function propertyName(name: string | ts.PropertyName): ts.PropertyName {
|
|
@@ -158,7 +188,9 @@ export function createPropertySignature({
|
|
|
158
188
|
type?: ts.TypeNode
|
|
159
189
|
}) {
|
|
160
190
|
return factory.createPropertySignature(
|
|
161
|
-
[...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : undefined].filter(
|
|
191
|
+
[...modifiers, readOnly ? factory.createToken(ts.SyntaxKind.ReadonlyKeyword) : undefined].filter(
|
|
192
|
+
(modifier): modifier is ts.Modifier => modifier !== undefined,
|
|
193
|
+
),
|
|
162
194
|
propertyName(name),
|
|
163
195
|
createQuestionToken(questionToken),
|
|
164
196
|
type,
|
|
@@ -192,7 +224,7 @@ export function createParameterSignature(
|
|
|
192
224
|
* Creates a JSDoc comment node from an array of comment strings.
|
|
193
225
|
* Returns null if no comments are provided.
|
|
194
226
|
*/
|
|
195
|
-
export function createJSDoc({ comments }: { comments: string
|
|
227
|
+
export function createJSDoc({ comments }: { comments: Array<string> }) {
|
|
196
228
|
if (!comments.length) {
|
|
197
229
|
return null
|
|
198
230
|
}
|
|
@@ -329,18 +361,6 @@ export function createTypeDeclaration({
|
|
|
329
361
|
})
|
|
330
362
|
}
|
|
331
363
|
|
|
332
|
-
/**
|
|
333
|
-
* Creates a TypeScript namespace declaration (exported module).
|
|
334
|
-
*/
|
|
335
|
-
export function createNamespaceDeclaration({ statements, name }: { name: string; statements: ts.Statement[] }) {
|
|
336
|
-
return factory.createModuleDeclaration(
|
|
337
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
338
|
-
factory.createIdentifier(name),
|
|
339
|
-
factory.createModuleBlock(statements),
|
|
340
|
-
ts.NodeFlags.Namespace,
|
|
341
|
-
)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
364
|
/**
|
|
345
365
|
* Creates an import declaration with support for default imports, named imports, namespace imports, and type-only imports.
|
|
346
366
|
* Optionally rename imported members with `propertyName` and `name` pairs.
|
|
@@ -366,13 +386,8 @@ export function createImportDeclaration({
|
|
|
366
386
|
isNameSpace?: boolean
|
|
367
387
|
}) {
|
|
368
388
|
if (!Array.isArray(name)) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (isNameSpace) {
|
|
373
|
-
importPropertyName = undefined
|
|
374
|
-
importName = factory.createNamespaceImport(factory.createIdentifier(name))
|
|
375
|
-
}
|
|
389
|
+
const importPropertyName = isNameSpace ? undefined : factory.createIdentifier(name)
|
|
390
|
+
const importName = isNameSpace ? factory.createNamespaceImport(factory.createIdentifier(name)) : undefined
|
|
376
391
|
|
|
377
392
|
return factory.createImportDeclaration(
|
|
378
393
|
undefined,
|
|
@@ -383,7 +398,7 @@ export function createImportDeclaration({
|
|
|
383
398
|
}
|
|
384
399
|
|
|
385
400
|
// Sort the imports alphabetically for consistent output across platforms
|
|
386
|
-
const sortedName =
|
|
401
|
+
const sortedName = name.toSorted((a, b) => compareStrings(typeof a === 'object' ? a.propertyName : a, typeof b === 'object' ? b.propertyName : b))
|
|
387
402
|
|
|
388
403
|
return factory.createImportDeclaration(
|
|
389
404
|
undefined,
|
|
@@ -442,7 +457,7 @@ export function createExportDeclaration({
|
|
|
442
457
|
}
|
|
443
458
|
|
|
444
459
|
// Sort the exports alphabetically for consistent output across platforms
|
|
445
|
-
const sortedName =
|
|
460
|
+
const sortedName = name.toSorted((a, b) => compareStrings(typeof a === 'string' ? a : a.text, typeof b === 'string' ? b : b.text))
|
|
446
461
|
|
|
447
462
|
return factory.createExportDeclaration(
|
|
448
463
|
undefined,
|
|
@@ -501,15 +516,14 @@ export function createEnumDeclaration({
|
|
|
501
516
|
enumKeyCasing = 'none',
|
|
502
517
|
}: {
|
|
503
518
|
/**
|
|
504
|
-
* Choose to use `enum`, `asConst`, `
|
|
519
|
+
* Choose to use `enum`, `asConst`, `constEnum`, or `literal` for enums.
|
|
505
520
|
* - `enum`: TypeScript enum
|
|
506
|
-
* - `asConst`: const with
|
|
507
|
-
* - `asPascalConst`: const with PascalCase name (e.g., `PetType`)
|
|
521
|
+
* - `asConst`: const object asserted with `as const` (the caller decides the const name casing)
|
|
508
522
|
* - `constEnum`: const enum
|
|
509
523
|
* - `literal`: literal union type
|
|
510
524
|
* @default `'enum'`
|
|
511
525
|
*/
|
|
512
|
-
type?: 'enum' | 'asConst' | '
|
|
526
|
+
type?: 'enum' | 'asConst' | 'constEnum' | 'literal' | 'inlineLiteral'
|
|
513
527
|
/**
|
|
514
528
|
* Enum name in camelCase.
|
|
515
529
|
*/
|
|
@@ -518,7 +532,7 @@ export function createEnumDeclaration({
|
|
|
518
532
|
* Enum name in PascalCase.
|
|
519
533
|
*/
|
|
520
534
|
typeName: string
|
|
521
|
-
enums: [key: string | number, value: string | number | boolean]
|
|
535
|
+
enums: Array<[key: string | number, value: string | number | boolean]>
|
|
522
536
|
/**
|
|
523
537
|
* Choose the casing for enum key names.
|
|
524
538
|
* @default 'none'
|
|
@@ -553,7 +567,7 @@ export function createEnumDeclaration({
|
|
|
553
567
|
|
|
554
568
|
return undefined
|
|
555
569
|
})
|
|
556
|
-
.filter(
|
|
570
|
+
.filter((node): node is ts.LiteralTypeNode => node !== undefined),
|
|
557
571
|
),
|
|
558
572
|
),
|
|
559
573
|
]
|
|
@@ -563,7 +577,9 @@ export function createEnumDeclaration({
|
|
|
563
577
|
return [
|
|
564
578
|
undefined,
|
|
565
579
|
factory.createEnumDeclaration(
|
|
566
|
-
[factory.createToken(ts.SyntaxKind.ExportKeyword), type === 'constEnum' ? factory.createToken(ts.SyntaxKind.ConstKeyword) : undefined].filter(
|
|
580
|
+
[factory.createToken(ts.SyntaxKind.ExportKeyword), type === 'constEnum' ? factory.createToken(ts.SyntaxKind.ConstKeyword) : undefined].filter(
|
|
581
|
+
(modifier): modifier is ts.ModifierToken<ts.SyntaxKind.ExportKeyword> | ts.ModifierToken<ts.SyntaxKind.ConstKeyword> => modifier !== undefined,
|
|
582
|
+
),
|
|
567
583
|
factory.createIdentifier(typeName),
|
|
568
584
|
enums
|
|
569
585
|
.map(([key, value]) => {
|
|
@@ -594,14 +610,14 @@ export function createEnumDeclaration({
|
|
|
594
610
|
|
|
595
611
|
return undefined
|
|
596
612
|
})
|
|
597
|
-
.filter(
|
|
613
|
+
.filter((member): member is ts.EnumMember => member !== undefined),
|
|
598
614
|
),
|
|
599
615
|
]
|
|
600
616
|
}
|
|
601
617
|
|
|
602
618
|
// used when using `as const` instead of an TypeScript enum.
|
|
603
|
-
// name is already
|
|
604
|
-
// 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.
|
|
605
621
|
const identifierName = name
|
|
606
622
|
|
|
607
623
|
// When there are no enum items (empty or all-null enum), don't generate a runtime const.
|
|
@@ -657,7 +673,7 @@ export function createEnumDeclaration({
|
|
|
657
673
|
|
|
658
674
|
return undefined
|
|
659
675
|
})
|
|
660
|
-
.filter(
|
|
676
|
+
.filter((property): property is ts.PropertyAssignment => property !== undefined),
|
|
661
677
|
true,
|
|
662
678
|
),
|
|
663
679
|
factory.createTypeReferenceNode(factory.createIdentifier('const'), undefined),
|
|
@@ -737,8 +753,8 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
|
|
|
737
753
|
}
|
|
738
754
|
|
|
739
755
|
const segments = normalized.split(/(\{[^}]+\})/)
|
|
740
|
-
const parts: string
|
|
741
|
-
const parameterIndices: number
|
|
756
|
+
const parts: Array<string> = []
|
|
757
|
+
const parameterIndices: Array<number> = []
|
|
742
758
|
|
|
743
759
|
segments.forEach((segment) => {
|
|
744
760
|
if (segment.startsWith('{') && segment.endsWith('}')) {
|
|
@@ -750,7 +766,7 @@ export function createUrlTemplateType(path: string): ts.TypeNode {
|
|
|
750
766
|
})
|
|
751
767
|
|
|
752
768
|
const head = ts.factory.createTemplateHead(parts[0] || '')
|
|
753
|
-
const templateSpans: ts.TemplateLiteralTypeSpan
|
|
769
|
+
const templateSpans: Array<ts.TemplateLiteralTypeSpan> = []
|
|
754
770
|
|
|
755
771
|
parameterIndices.forEach((paramIndex, i) => {
|
|
756
772
|
const isLast = i === parameterIndices.length - 1
|
|
@@ -787,21 +803,11 @@ export const createStringLiteral = factory.createStringLiteral
|
|
|
787
803
|
*/
|
|
788
804
|
export const createArrayTypeNode = factory.createArrayTypeNode
|
|
789
805
|
|
|
790
|
-
/**
|
|
791
|
-
* Creates a parenthesized type node to control operator precedence.
|
|
792
|
-
*/
|
|
793
|
-
export const createParenthesizedType = factory.createParenthesizedType
|
|
794
|
-
|
|
795
806
|
/**
|
|
796
807
|
* Creates a literal type node (e.g., `'hello'`, `42`, `true`).
|
|
797
808
|
*/
|
|
798
809
|
export const createLiteralTypeNode = factory.createLiteralTypeNode
|
|
799
810
|
|
|
800
|
-
/**
|
|
801
|
-
* Creates a null literal type node.
|
|
802
|
-
*/
|
|
803
|
-
export const createNull = factory.createNull
|
|
804
|
-
|
|
805
811
|
/**
|
|
806
812
|
* Creates an identifier node.
|
|
807
813
|
*/
|
|
@@ -832,16 +838,6 @@ export const createTrue = factory.createTrue
|
|
|
832
838
|
*/
|
|
833
839
|
export const createFalse = factory.createFalse
|
|
834
840
|
|
|
835
|
-
/**
|
|
836
|
-
* Creates an indexed access type node (e.g., `T[K]`).
|
|
837
|
-
*/
|
|
838
|
-
export const createIndexedAccessTypeNode = factory.createIndexedAccessTypeNode
|
|
839
|
-
|
|
840
|
-
/**
|
|
841
|
-
* Creates a type operator node (e.g., `keyof T`, `readonly T[]`).
|
|
842
|
-
*/
|
|
843
|
-
export const createTypeOperatorNode = factory.createTypeOperatorNode
|
|
844
|
-
|
|
845
841
|
/**
|
|
846
842
|
* Creates a prefix unary expression (e.g., negative numbers, logical not).
|
|
847
843
|
*/
|
|
@@ -885,7 +881,7 @@ export function buildMemberNodes(
|
|
|
885
881
|
members: Array<ast.SchemaNode> | undefined,
|
|
886
882
|
print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined,
|
|
887
883
|
): Array<ts.TypeNode> {
|
|
888
|
-
return (members ?? []).map(print).filter(
|
|
884
|
+
return (members ?? []).map(print).filter(isNonNullable)
|
|
889
885
|
}
|
|
890
886
|
|
|
891
887
|
/**
|
|
@@ -893,7 +889,7 @@ export function buildMemberNodes(
|
|
|
893
889
|
* applying min/max slice and optional/rest element rules.
|
|
894
890
|
*/
|
|
895
891
|
export function buildTupleNode(node: ast.ArraySchemaNode, print: (node: ast.SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
|
|
896
|
-
let items = (node.items ?? []).map(print).filter(
|
|
892
|
+
let items = (node.items ?? []).map(print).filter(isNonNullable)
|
|
897
893
|
|
|
898
894
|
const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
|
|
899
895
|
const { min, max } = node
|