@kubb/plugin-ts 5.0.0-alpha.2 → 5.0.0-alpha.21
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/Type-B6fo0gSk.js +120 -0
- package/dist/Type-B6fo0gSk.js.map +1 -0
- package/dist/Type-oFwUfkZv.cjs +131 -0
- package/dist/Type-oFwUfkZv.cjs.map +1 -0
- package/dist/builderTs-Cd3juc2G.cjs +120 -0
- package/dist/builderTs-Cd3juc2G.cjs.map +1 -0
- package/dist/builderTs-DausqHpc.js +116 -0
- package/dist/builderTs-DausqHpc.js.map +1 -0
- package/dist/builders.cjs +3 -0
- package/dist/builders.d.ts +8 -0
- package/dist/builders.js +2 -0
- package/dist/casing-BJHFg-zZ.js +84 -0
- package/dist/casing-BJHFg-zZ.js.map +1 -0
- package/dist/casing-DHfdqpLi.cjs +107 -0
- package/dist/casing-DHfdqpLi.cjs.map +1 -0
- package/dist/chunk-ByKO4r7w.cjs +38 -0
- package/dist/components.cjs +3 -2
- package/dist/components.d.ts +40 -11
- package/dist/components.js +2 -2
- package/dist/generators-ByK18qUn.js +551 -0
- package/dist/generators-ByK18qUn.js.map +1 -0
- package/dist/generators-aSsiTfUO.cjs +563 -0
- package/dist/generators-aSsiTfUO.cjs.map +1 -0
- package/dist/generators.cjs +3 -2
- package/dist/generators.d.ts +7 -492
- package/dist/generators.js +2 -2
- package/dist/index.cjs +148 -3
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +146 -1
- package/dist/index.js.map +1 -0
- package/dist/printerTs-BgZucv4T.js +559 -0
- package/dist/printerTs-BgZucv4T.js.map +1 -0
- package/dist/printerTs-CFXc_LpP.cjs +595 -0
- package/dist/printerTs-CFXc_LpP.cjs.map +1 -0
- package/dist/printers.cjs +3 -0
- package/dist/printers.d.ts +75 -0
- package/dist/printers.js +2 -0
- package/dist/resolverTsLegacy-DLl854-P.js +185 -0
- package/dist/resolverTsLegacy-DLl854-P.js.map +1 -0
- package/dist/resolverTsLegacy-sJ16Iqrl.cjs +196 -0
- package/dist/resolverTsLegacy-sJ16Iqrl.cjs.map +1 -0
- package/dist/resolvers.cjs +4 -0
- package/dist/resolvers.d.ts +52 -0
- package/dist/resolvers.js +2 -0
- package/dist/types-BcyuFDn9.d.ts +344 -0
- package/package.json +27 -8
- package/src/builders/builderTs.ts +92 -0
- package/src/builders/index.ts +1 -0
- package/src/components/Enum.tsx +83 -0
- package/src/components/Type.tsx +24 -145
- package/src/components/index.ts +1 -0
- package/src/constants.ts +29 -0
- package/src/factory.ts +14 -48
- package/src/generators/index.ts +1 -0
- package/src/generators/typeGenerator.tsx +119 -403
- package/src/generators/typeGeneratorLegacy.tsx +345 -0
- package/src/plugin.ts +80 -122
- package/src/presets.ts +26 -0
- package/src/printers/index.ts +1 -0
- package/src/printers/printerTs.ts +389 -0
- package/src/resolvers/index.ts +2 -0
- package/src/resolvers/resolverTs.ts +107 -0
- package/src/resolvers/resolverTsLegacy.ts +87 -0
- package/src/types.ts +261 -72
- package/dist/components-9wydyqUx.cjs +0 -848
- package/dist/components-9wydyqUx.cjs.map +0 -1
- package/dist/components-LmqJfxMv.js +0 -721
- package/dist/components-LmqJfxMv.js.map +0 -1
- package/dist/plugin-CNkzbtpl.cjs +0 -508
- package/dist/plugin-CNkzbtpl.cjs.map +0 -1
- package/dist/plugin-DoLrDl9P.js +0 -476
- package/dist/plugin-DoLrDl9P.js.map +0 -1
- package/dist/types-BpeKGgCn.d.ts +0 -170
- package/src/parser.ts +0 -396
- package/src/printer.ts +0 -221
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { camelCase, trimQuotes } from '@internals/utils'
|
|
2
|
+
import type { EnumSchemaNode } from '@kubb/ast/types'
|
|
3
|
+
import { safePrint } from '@kubb/fabric-core/parsers/typescript'
|
|
4
|
+
import { File } from '@kubb/react-fabric'
|
|
5
|
+
import type { FabricReactNode } from '@kubb/react-fabric/types'
|
|
6
|
+
import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_WITH_TYPE_ONLY } from '../constants.ts'
|
|
7
|
+
import * as factory from '../factory.ts'
|
|
8
|
+
import type { PluginTs, ResolverTs } from '../types.ts'
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
node: EnumSchemaNode
|
|
12
|
+
enumType: PluginTs['resolvedOptions']['enumType']
|
|
13
|
+
enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
|
|
14
|
+
resolver: ResolverTs
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
|
|
19
|
+
*
|
|
20
|
+
* The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
|
|
21
|
+
* valid TypeScript identifier. The resolver normalizes it; for inline enum
|
|
22
|
+
* properties the adapter already emits a PascalCase+suffix name so resolution is typically a no-op.
|
|
23
|
+
*/
|
|
24
|
+
export function getEnumNames({ node, enumType, resolver }: { node: EnumSchemaNode; enumType: PluginTs['resolvedOptions']['enumType']; resolver: ResolverTs }): {
|
|
25
|
+
enumName: string
|
|
26
|
+
typeName: string
|
|
27
|
+
/**
|
|
28
|
+
* The PascalCase name that `$ref` importers will use to reference this enum type.
|
|
29
|
+
* For `asConst`/`asPascalConst` this differs from `typeName` (which has a `Key` suffix).
|
|
30
|
+
*/
|
|
31
|
+
refName: string
|
|
32
|
+
} {
|
|
33
|
+
const resolved = resolver.default(node.name!, 'type')
|
|
34
|
+
const enumName = enumType === 'asPascalConst' ? resolved : camelCase(node.name!)
|
|
35
|
+
const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? `${resolved}Key` : resolved
|
|
36
|
+
|
|
37
|
+
return { enumName, typeName, refName: resolved }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Renders the enum declaration(s) for a single named `EnumSchemaNode`.
|
|
42
|
+
*
|
|
43
|
+
* Depending on `enumType` this may emit:
|
|
44
|
+
* - A runtime object (`asConst` / `asPascalConst`) plus a `typeof` type alias
|
|
45
|
+
* - A `const enum` or plain `enum` declaration (`constEnum` / `enum`)
|
|
46
|
+
* - A union literal type alias (`literal`)
|
|
47
|
+
*
|
|
48
|
+
* The emitted `File.Source` nodes carry the resolved names so that the barrel
|
|
49
|
+
* index picks up the correct export identifiers.
|
|
50
|
+
*/
|
|
51
|
+
export function Enum({ node, enumType, enumKeyCasing, resolver }: Props): FabricReactNode {
|
|
52
|
+
const { enumName, typeName, refName } = getEnumNames({ node, enumType, resolver })
|
|
53
|
+
|
|
54
|
+
const [nameNode, typeNode] = factory.createEnumDeclaration({
|
|
55
|
+
name: enumName,
|
|
56
|
+
typeName,
|
|
57
|
+
enums: (node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ??
|
|
58
|
+
node.enumValues?.filter((v): v is NonNullable<typeof v> => v !== null && v !== undefined).map((v) => [trimQuotes(v.toString()), v]) ??
|
|
59
|
+
[]) as unknown as Array<[string, string]>,
|
|
60
|
+
type: enumType,
|
|
61
|
+
enumKeyCasing,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const needsRefAlias = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && refName !== typeName
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
{nameNode && (
|
|
69
|
+
<File.Source name={enumName} isExportable isIndexable isTypeOnly={false}>
|
|
70
|
+
{safePrint(nameNode)}
|
|
71
|
+
</File.Source>
|
|
72
|
+
)}
|
|
73
|
+
<File.Source name={typeName} isIndexable isExportable={ENUM_TYPES_WITH_RUNTIME_VALUE.has(enumType)} isTypeOnly={ENUM_TYPES_WITH_TYPE_ONLY.has(enumType)}>
|
|
74
|
+
{safePrint(typeNode)}
|
|
75
|
+
</File.Source>
|
|
76
|
+
{needsRefAlias && (
|
|
77
|
+
<File.Source name={refName} isExportable isIndexable isTypeOnly>
|
|
78
|
+
{`export type ${refName} = ${typeName}`}
|
|
79
|
+
</File.Source>
|
|
80
|
+
)}
|
|
81
|
+
</>
|
|
82
|
+
)
|
|
83
|
+
}
|
package/src/components/Type.tsx
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { SchemaObject } from '@kubb/oas'
|
|
4
|
-
import { isKeyword, type Schema, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
|
|
1
|
+
import { collect, narrowSchema, schemaTypes } from '@kubb/ast'
|
|
2
|
+
import type { EnumSchemaNode, SchemaNode } from '@kubb/ast/types'
|
|
5
3
|
import { File } from '@kubb/react-fabric'
|
|
6
4
|
import type { FabricReactNode } from '@kubb/react-fabric/types'
|
|
7
|
-
import
|
|
8
|
-
import * as factory from '../factory.ts'
|
|
9
|
-
import { parse, typeKeywordMapper } from '../parser.ts'
|
|
5
|
+
import { printerTs } from '../printers/printerTs.ts'
|
|
10
6
|
import type { PluginTs } from '../types.ts'
|
|
7
|
+
import { Enum, getEnumNames } from './Enum.tsx'
|
|
11
8
|
|
|
12
9
|
type Props = {
|
|
13
10
|
name: string
|
|
14
11
|
typedName: string
|
|
15
|
-
|
|
16
|
-
tree: Array<Schema>
|
|
12
|
+
node: SchemaNode
|
|
17
13
|
optionalType: PluginTs['resolvedOptions']['optionalType']
|
|
18
14
|
arrayType: PluginTs['resolvedOptions']['arrayType']
|
|
19
15
|
enumType: PluginTs['resolvedOptions']['enumType']
|
|
20
16
|
enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
|
|
21
|
-
mapper: PluginTs['resolvedOptions']['mapper']
|
|
22
17
|
syntaxType: PluginTs['resolvedOptions']['syntaxType']
|
|
18
|
+
resolver: PluginTs['resolvedOptions']['resolver']
|
|
23
19
|
description?: string
|
|
24
20
|
keysToOmit?: string[]
|
|
25
21
|
}
|
|
@@ -27,133 +23,35 @@ type Props = {
|
|
|
27
23
|
export function Type({
|
|
28
24
|
name,
|
|
29
25
|
typedName,
|
|
30
|
-
|
|
26
|
+
node,
|
|
31
27
|
keysToOmit,
|
|
32
|
-
schema,
|
|
33
28
|
optionalType,
|
|
34
29
|
arrayType,
|
|
35
30
|
syntaxType,
|
|
36
31
|
enumType,
|
|
37
32
|
enumKeyCasing,
|
|
38
|
-
mapper,
|
|
39
33
|
description,
|
|
34
|
+
resolver,
|
|
40
35
|
}: Props): FabricReactNode {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const enumSchemas = SchemaGenerator.deepSearch(tree, schemaKeywords.enum)
|
|
49
|
-
|
|
50
|
-
let type =
|
|
51
|
-
(tree
|
|
52
|
-
.map((current, _index, siblings) =>
|
|
53
|
-
parse(
|
|
54
|
-
{ name, schema, parent: undefined, current, siblings },
|
|
55
|
-
{
|
|
56
|
-
optionalType,
|
|
57
|
-
arrayType,
|
|
58
|
-
enumType,
|
|
59
|
-
mapper,
|
|
60
|
-
},
|
|
61
|
-
),
|
|
62
|
-
)
|
|
63
|
-
.filter(Boolean)
|
|
64
|
-
.at(0) as ts.TypeNode) || typeKeywordMapper.undefined()
|
|
65
|
-
|
|
66
|
-
// Add a "Key" suffix to avoid collisions where necessary
|
|
67
|
-
if (['asConst', 'asPascalConst'].includes(enumType) && enumSchemas.length > 0) {
|
|
68
|
-
const isDirectEnum = schema.type === 'array' && schema.items !== undefined
|
|
69
|
-
const isEnumOnly = 'enum' in schema && schema.enum
|
|
70
|
-
|
|
71
|
-
if (isDirectEnum || isEnumOnly) {
|
|
72
|
-
const enumSchema = enumSchemas[0]!
|
|
73
|
-
const typeNameWithKey = `${enumSchema.args.typeName}Key`
|
|
74
|
-
|
|
75
|
-
type = factory.createTypeReferenceNode(typeNameWithKey)
|
|
76
|
-
|
|
77
|
-
if (schema.type === 'array') {
|
|
78
|
-
if (arrayType === 'generic') {
|
|
79
|
-
type = factory.createTypeReferenceNode(factory.createIdentifier('Array'), [type])
|
|
80
|
-
} else {
|
|
81
|
-
type = factory.createArrayTypeNode(type)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (schemaFromTree && isKeyword(schemaFromTree, schemaKeywords.schema)) {
|
|
88
|
-
const isNullish = tree.some((item) => item.keyword === schemaKeywords.nullish)
|
|
89
|
-
const isNullable = tree.some((item) => item.keyword === schemaKeywords.nullable)
|
|
90
|
-
const isOptional = tree.some((item) => item.keyword === schemaKeywords.optional)
|
|
36
|
+
const resolvedDescription = description || node?.description
|
|
37
|
+
const enumSchemaNodes = collect<EnumSchemaNode>(node, {
|
|
38
|
+
schema(n): EnumSchemaNode | undefined {
|
|
39
|
+
const enumNode = narrowSchema(n, schemaTypes.enum)
|
|
40
|
+
if (enumNode?.name) return enumNode
|
|
41
|
+
},
|
|
42
|
+
})
|
|
91
43
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
nodes: [type, factory.keywordTypeNodes.null],
|
|
95
|
-
}) as ts.TypeNode
|
|
96
|
-
}
|
|
44
|
+
const printer = printerTs({ optionalType, arrayType, enumType, typeName: name, syntaxType, description: resolvedDescription, keysToOmit, resolver })
|
|
45
|
+
const output = printer.print(node)
|
|
97
46
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
nodes: [type, factory.keywordTypeNodes.undefined],
|
|
101
|
-
}) as ts.TypeNode
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
|
|
105
|
-
type = factory.createUnionDeclaration({
|
|
106
|
-
nodes: [type, factory.keywordTypeNodes.undefined],
|
|
107
|
-
}) as ts.TypeNode
|
|
108
|
-
}
|
|
47
|
+
if (!output) {
|
|
48
|
+
return
|
|
109
49
|
}
|
|
110
50
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
typeNodes.push(
|
|
114
|
-
factory.createTypeDeclaration({
|
|
115
|
-
name,
|
|
116
|
-
isExportable: true,
|
|
117
|
-
type: keysToOmit?.length
|
|
118
|
-
? factory.createOmitDeclaration({
|
|
119
|
-
keys: keysToOmit,
|
|
120
|
-
type,
|
|
121
|
-
nonNullable: true,
|
|
122
|
-
})
|
|
123
|
-
: type,
|
|
124
|
-
syntax: useTypeGeneration ? 'type' : 'interface',
|
|
125
|
-
comments: [
|
|
126
|
-
schema.title ? `${jsStringEscape(schema.title)}` : undefined,
|
|
127
|
-
description ? `@description ${jsStringEscape(description)}` : undefined,
|
|
128
|
-
schema.deprecated ? '@deprecated' : undefined,
|
|
129
|
-
schema.minLength ? `@minLength ${schema.minLength}` : undefined,
|
|
130
|
-
schema.maxLength ? `@maxLength ${schema.maxLength}` : undefined,
|
|
131
|
-
schema.pattern ? `@pattern ${schema.pattern}` : undefined,
|
|
132
|
-
schema.default ? `@default ${schema.default}` : undefined,
|
|
133
|
-
schema.example ? `@example ${schema.example}` : undefined,
|
|
134
|
-
],
|
|
135
|
-
}),
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
const enums = [...new Set(enumSchemas)].map((enumSchema) => {
|
|
139
|
-
const name = enumType === 'asPascalConst' ? pascalCase(enumSchema.args.name) : camelCase(enumSchema.args.name)
|
|
140
|
-
const typeName = ['asConst', 'asPascalConst'].includes(enumType) ? `${enumSchema.args.typeName}Key` : enumSchema.args.typeName
|
|
141
|
-
|
|
142
|
-
const [nameNode, typeNode] = factory.createEnumDeclaration({
|
|
143
|
-
name,
|
|
144
|
-
typeName,
|
|
145
|
-
enums: enumSchema.args.items
|
|
146
|
-
.map((item) => (item.value === undefined ? undefined : [trimQuotes(item.name?.toString()), item.value]))
|
|
147
|
-
.filter(Boolean) as unknown as Array<[string, string]>,
|
|
148
|
-
type: enumType,
|
|
149
|
-
enumKeyCasing,
|
|
150
|
-
})
|
|
151
|
-
|
|
51
|
+
const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
|
|
152
52
|
return {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
name,
|
|
156
|
-
typeName,
|
|
53
|
+
node,
|
|
54
|
+
...getEnumNames({ node, enumType, resolver }),
|
|
157
55
|
}
|
|
158
56
|
})
|
|
159
57
|
|
|
@@ -163,29 +61,10 @@ export function Type({
|
|
|
163
61
|
|
|
164
62
|
return (
|
|
165
63
|
<>
|
|
166
|
-
{shouldExportEnums &&
|
|
167
|
-
enums.map(({ name, nameNode, typeName, typeNode }) => (
|
|
168
|
-
<>
|
|
169
|
-
{nameNode && (
|
|
170
|
-
<File.Source name={name} isExportable isIndexable isTypeOnly={false}>
|
|
171
|
-
{safePrint(nameNode)}
|
|
172
|
-
</File.Source>
|
|
173
|
-
)}
|
|
174
|
-
{
|
|
175
|
-
<File.Source
|
|
176
|
-
name={typeName}
|
|
177
|
-
isIndexable
|
|
178
|
-
isExportable={['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined].includes(enumType)}
|
|
179
|
-
isTypeOnly={['asConst', 'asPascalConst', 'literal', undefined].includes(enumType)}
|
|
180
|
-
>
|
|
181
|
-
{safePrint(typeNode)}
|
|
182
|
-
</File.Source>
|
|
183
|
-
}
|
|
184
|
-
</>
|
|
185
|
-
))}
|
|
64
|
+
{shouldExportEnums && enums.map(({ node }) => <Enum node={node} enumType={enumType} enumKeyCasing={enumKeyCasing} resolver={resolver} />)}
|
|
186
65
|
{shouldExportType && (
|
|
187
66
|
<File.Source name={typedName} isTypeOnly isExportable isIndexable>
|
|
188
|
-
{
|
|
67
|
+
{output}
|
|
189
68
|
</File.Source>
|
|
190
69
|
)}
|
|
191
70
|
</>
|
package/src/components/index.ts
CHANGED
package/src/constants.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PluginTs } from './types.ts'
|
|
2
|
+
|
|
3
|
+
type OptionalType = PluginTs['resolvedOptions']['optionalType']
|
|
4
|
+
type EnumType = PluginTs['resolvedOptions']['enumType']
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `optionalType` values that cause a property's type to include `| undefined`.
|
|
8
|
+
*/
|
|
9
|
+
export const OPTIONAL_ADDS_UNDEFINED = new Set<OptionalType>(['undefined', 'questionTokenAndUndefined'] as const)
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `optionalType` values that render the property key with a `?` token.
|
|
13
|
+
*/
|
|
14
|
+
export const OPTIONAL_ADDS_QUESTION_TOKEN = new Set<OptionalType>(['questionToken', 'questionTokenAndUndefined'] as const)
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `enumType` values that append a `Key` suffix to the generated enum type alias.
|
|
18
|
+
*/
|
|
19
|
+
export const ENUM_TYPES_WITH_KEY_SUFFIX = new Set<EnumType>(['asConst', 'asPascalConst'] as const)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `enumType` values that require a runtime value declaration (object, enum, or literal).
|
|
23
|
+
*/
|
|
24
|
+
export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined] as const)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
|
|
28
|
+
*/
|
|
29
|
+
export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'asPascalConst', 'literal', undefined] as const)
|
package/src/factory.ts
CHANGED
|
@@ -15,18 +15,10 @@ export const modifiers = {
|
|
|
15
15
|
|
|
16
16
|
export const syntaxKind = {
|
|
17
17
|
union: SyntaxKind.UnionType as 192,
|
|
18
|
+
literalType: SyntaxKind.LiteralType,
|
|
19
|
+
stringLiteral: SyntaxKind.StringLiteral,
|
|
18
20
|
} as const
|
|
19
21
|
|
|
20
|
-
export function getUnknownType(unknownType: 'any' | 'unknown' | 'void' | undefined) {
|
|
21
|
-
if (unknownType === 'any') {
|
|
22
|
-
return keywordTypeNodes.any
|
|
23
|
-
}
|
|
24
|
-
if (unknownType === 'void') {
|
|
25
|
-
return keywordTypeNodes.void
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return keywordTypeNodes.unknown
|
|
29
|
-
}
|
|
30
22
|
function isValidIdentifier(str: string): boolean {
|
|
31
23
|
if (!str.length || str.trim() !== str) {
|
|
32
24
|
return false
|
|
@@ -74,28 +66,6 @@ export function createIntersectionDeclaration({ nodes, withParentheses }: { node
|
|
|
74
66
|
return node
|
|
75
67
|
}
|
|
76
68
|
|
|
77
|
-
/**
|
|
78
|
-
* Minimum nodes length of 2
|
|
79
|
-
* @example `string & number`
|
|
80
|
-
*/
|
|
81
|
-
export function createTupleDeclaration({ nodes, withParentheses }: { nodes: Array<ts.TypeNode>; withParentheses?: boolean }): ts.TypeNode | null {
|
|
82
|
-
if (!nodes.length) {
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (nodes.length === 1) {
|
|
87
|
-
return nodes[0] || null
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const node = factory.createTupleTypeNode(nodes)
|
|
91
|
-
|
|
92
|
-
if (withParentheses) {
|
|
93
|
-
return factory.createParenthesizedType(node)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return node
|
|
97
|
-
}
|
|
98
|
-
|
|
99
69
|
export function createArrayDeclaration({ nodes, arrayType = 'array' }: { nodes: Array<ts.TypeNode>; arrayType?: 'array' | 'generic' }): ts.TypeNode | null {
|
|
100
70
|
if (!nodes.length) {
|
|
101
71
|
return factory.createTupleTypeNode([])
|
|
@@ -660,43 +630,39 @@ export const keywordTypeNodes = {
|
|
|
660
630
|
* Converts a path like '/pet/{petId}/uploadImage' to a template literal type
|
|
661
631
|
* like `/pet/${string}/uploadImage`
|
|
662
632
|
*/
|
|
633
|
+
/**
|
|
634
|
+
* Converts an OAS-style path (e.g. `/pets/{petId}`) or an Express-style path
|
|
635
|
+
* (e.g. `/pets/:petId`) to a TypeScript template literal type
|
|
636
|
+
* like `` `/pets/${string}` ``.
|
|
637
|
+
*/
|
|
663
638
|
export function createUrlTemplateType(path: string): ts.TypeNode {
|
|
664
|
-
//
|
|
665
|
-
|
|
666
|
-
return factory.createLiteralTypeNode(factory.createStringLiteral(path))
|
|
667
|
-
}
|
|
639
|
+
// normalized Express `:param` → OAS `{param}` so a single regex handles both.
|
|
640
|
+
const normalized = path.replace(/:([^/]+)/g, '{$1}')
|
|
668
641
|
|
|
669
|
-
|
|
670
|
-
|
|
642
|
+
if (!normalized.includes('{')) {
|
|
643
|
+
return factory.createLiteralTypeNode(factory.createStringLiteral(normalized))
|
|
644
|
+
}
|
|
671
645
|
|
|
672
|
-
|
|
646
|
+
const segments = normalized.split(/(\{[^}]+\})/)
|
|
673
647
|
const parts: string[] = []
|
|
674
648
|
const parameterIndices: number[] = []
|
|
675
649
|
|
|
676
650
|
segments.forEach((segment) => {
|
|
677
651
|
if (segment.startsWith('{') && segment.endsWith('}')) {
|
|
678
|
-
// This is a parameter placeholder
|
|
679
652
|
parameterIndices.push(parts.length)
|
|
680
|
-
parts.push(segment)
|
|
653
|
+
parts.push(segment)
|
|
681
654
|
} else if (segment) {
|
|
682
|
-
// This is a static part
|
|
683
655
|
parts.push(segment)
|
|
684
656
|
}
|
|
685
657
|
})
|
|
686
658
|
|
|
687
|
-
// Build template literal type
|
|
688
|
-
// Template literal structure: head + templateSpans[]
|
|
689
|
-
// For '/pet/{petId}/upload': head = '/pet/', spans = [{ type: string, literal: '/upload' }]
|
|
690
|
-
|
|
691
659
|
const head = ts.factory.createTemplateHead(parts[0] || '')
|
|
692
660
|
const templateSpans: ts.TemplateLiteralTypeSpan[] = []
|
|
693
661
|
|
|
694
662
|
parameterIndices.forEach((paramIndex, i) => {
|
|
695
663
|
const isLast = i === parameterIndices.length - 1
|
|
696
664
|
const nextPart = parts[paramIndex + 1] || ''
|
|
697
|
-
|
|
698
665
|
const literal = isLast ? ts.factory.createTemplateTail(nextPart) : ts.factory.createTemplateMiddle(nextPart)
|
|
699
|
-
|
|
700
666
|
templateSpans.push(ts.factory.createTemplateLiteralTypeSpan(keywordTypeNodes.string, literal))
|
|
701
667
|
})
|
|
702
668
|
|
package/src/generators/index.ts
CHANGED