@kubb/plugin-ts 5.0.0-alpha.3 → 5.0.0-alpha.31
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 +1806 -3
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +590 -4
- package/dist/index.js +1776 -2
- package/dist/index.js.map +1 -0
- package/package.json +7 -27
- package/src/components/Enum.tsx +82 -0
- package/src/components/Type.tsx +29 -162
- package/src/constants.ts +39 -0
- package/src/factory.ts +134 -49
- package/src/generators/typeGenerator.tsx +165 -428
- package/src/generators/typeGeneratorLegacy.tsx +349 -0
- package/src/index.ts +9 -1
- package/src/plugin.ts +98 -176
- package/src/presets.ts +28 -0
- package/src/printers/functionPrinter.ts +196 -0
- package/src/printers/printerTs.ts +310 -0
- package/src/resolvers/resolverTs.ts +66 -0
- package/src/resolvers/resolverTsLegacy.ts +60 -0
- package/src/types.ts +258 -98
- package/src/utils.ts +131 -0
- package/dist/components-CRjwjdyE.js +0 -725
- package/dist/components-CRjwjdyE.js.map +0 -1
- package/dist/components-DI0aTIBg.cjs +0 -978
- package/dist/components-DI0aTIBg.cjs.map +0 -1
- package/dist/components.cjs +0 -3
- package/dist/components.d.ts +0 -38
- package/dist/components.js +0 -2
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -503
- package/dist/generators.js +0 -2
- package/dist/plugin-D5rCK1zO.cjs +0 -992
- package/dist/plugin-D5rCK1zO.cjs.map +0 -1
- package/dist/plugin-DmwgRHK8.js +0 -944
- package/dist/plugin-DmwgRHK8.js.map +0 -1
- package/dist/types-BpeKGgCn.d.ts +0 -170
- package/src/components/index.ts +0 -1
- package/src/components/v2/Type.tsx +0 -165
- package/src/generators/index.ts +0 -2
- package/src/generators/v2/typeGenerator.tsx +0 -196
- package/src/parser.ts +0 -396
- package/src/printer.ts +0 -244
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import type { SchemaNode } from '@kubb/ast/types'
|
|
2
|
-
import { useKubb } from '@kubb/core/hooks'
|
|
3
|
-
import { createReactGenerator } from '@kubb/plugin-oas/generators'
|
|
4
|
-
import { File } from '@kubb/react-fabric'
|
|
5
|
-
import { Type } from '../../components/v2/Type.tsx'
|
|
6
|
-
import type { PluginTs } from '../../types'
|
|
7
|
-
|
|
8
|
-
export const typeGenerator = createReactGenerator<PluginTs, '2'>({
|
|
9
|
-
name: 'typescript',
|
|
10
|
-
version: '2',
|
|
11
|
-
Operation({ node, adapter, options }) {
|
|
12
|
-
const { enumType, enumKeyCasing, optionalType, arrayType, syntaxType } = options
|
|
13
|
-
|
|
14
|
-
const { plugin, mode, getFile, resolveName } = useKubb<PluginTs>()
|
|
15
|
-
|
|
16
|
-
const file = getFile({
|
|
17
|
-
name: node.operationId,
|
|
18
|
-
pluginName: plugin.name,
|
|
19
|
-
extname: '.ts',
|
|
20
|
-
mode,
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
function renderSchemaType({ node: schemaNode, name, typedName, description }: { node: SchemaNode; name: string; typedName: string; description?: string }) {
|
|
24
|
-
const imports = adapter.getImports(schemaNode, (schemaName) => ({
|
|
25
|
-
name: resolveName({
|
|
26
|
-
name: schemaName,
|
|
27
|
-
pluginName: plugin.name,
|
|
28
|
-
type: 'type',
|
|
29
|
-
}),
|
|
30
|
-
path: getFile({
|
|
31
|
-
name: schemaName,
|
|
32
|
-
pluginName: plugin.name,
|
|
33
|
-
extname: '.ts',
|
|
34
|
-
mode,
|
|
35
|
-
}).path,
|
|
36
|
-
}))
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<>
|
|
40
|
-
{mode === 'split' &&
|
|
41
|
-
imports.map((imp) => <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={file.path} path={imp.path} name={imp.name} isTypeOnly />)}
|
|
42
|
-
|
|
43
|
-
<Type
|
|
44
|
-
name={name}
|
|
45
|
-
typedName={typedName}
|
|
46
|
-
node={schemaNode}
|
|
47
|
-
description={description}
|
|
48
|
-
enumType={enumType}
|
|
49
|
-
enumKeyCasing={enumKeyCasing}
|
|
50
|
-
optionalType={optionalType}
|
|
51
|
-
arrayType={arrayType}
|
|
52
|
-
syntaxType={syntaxType}
|
|
53
|
-
/>
|
|
54
|
-
</>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Parameter types — each parameter rendered as its own type
|
|
59
|
-
const paramTypes = node.parameters.map((param) => {
|
|
60
|
-
const name = resolveName({
|
|
61
|
-
name: `${node.operationId} ${param.name}`,
|
|
62
|
-
pluginName: plugin.name,
|
|
63
|
-
type: 'function',
|
|
64
|
-
})
|
|
65
|
-
const typedName = resolveName({
|
|
66
|
-
name: `${node.operationId} ${param.name}`,
|
|
67
|
-
pluginName: plugin.name,
|
|
68
|
-
type: 'type',
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
return renderSchemaType({ node: param.schema, name, typedName })
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
// Response types
|
|
75
|
-
const responseTypes = node.responses
|
|
76
|
-
.filter((res) => res.schema)
|
|
77
|
-
.map((res) => {
|
|
78
|
-
const schemaNode = res.schema!
|
|
79
|
-
const responseName = `${node.operationId} ${res.statusCode}`
|
|
80
|
-
const resolvedName = resolveName({
|
|
81
|
-
name: responseName,
|
|
82
|
-
pluginName: plugin.name,
|
|
83
|
-
type: 'function',
|
|
84
|
-
})
|
|
85
|
-
const typedName = resolveName({
|
|
86
|
-
name: responseName,
|
|
87
|
-
pluginName: plugin.name,
|
|
88
|
-
type: 'type',
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
return renderSchemaType({ node: schemaNode, name: resolvedName, typedName, description: res.description })
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Request body type
|
|
95
|
-
const requestType = node.requestBody
|
|
96
|
-
? (() => {
|
|
97
|
-
const requestName = `${node.operationId} MutationRequest`
|
|
98
|
-
const resolvedName = resolveName({
|
|
99
|
-
name: requestName,
|
|
100
|
-
pluginName: plugin.name,
|
|
101
|
-
type: 'function',
|
|
102
|
-
})
|
|
103
|
-
const typedName = resolveName({
|
|
104
|
-
name: requestName,
|
|
105
|
-
pluginName: plugin.name,
|
|
106
|
-
type: 'type',
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
return renderSchemaType({ node: node.requestBody, name: resolvedName, typedName, description: node.requestBody.description })
|
|
110
|
-
})()
|
|
111
|
-
: null
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<File baseName={file.baseName} path={file.path} meta={file.meta}>
|
|
115
|
-
{paramTypes}
|
|
116
|
-
{responseTypes}
|
|
117
|
-
{requestType}
|
|
118
|
-
</File>
|
|
119
|
-
)
|
|
120
|
-
},
|
|
121
|
-
Schema({ node, adapter, options }) {
|
|
122
|
-
const { enumType, enumKeyCasing, syntaxType, optionalType, arrayType } = options
|
|
123
|
-
const { plugin, mode, resolveName, getFile } = useKubb<PluginTs>()
|
|
124
|
-
|
|
125
|
-
if (!node.name) {
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const imports = adapter.getImports(node, (schemaName) => ({
|
|
130
|
-
name: resolveName({
|
|
131
|
-
name: schemaName,
|
|
132
|
-
pluginName: plugin.name,
|
|
133
|
-
type: 'type',
|
|
134
|
-
}),
|
|
135
|
-
path: getFile({
|
|
136
|
-
name: schemaName,
|
|
137
|
-
pluginName: plugin.name,
|
|
138
|
-
extname: '.ts',
|
|
139
|
-
mode,
|
|
140
|
-
// options: {
|
|
141
|
-
// group
|
|
142
|
-
// },
|
|
143
|
-
}).path,
|
|
144
|
-
}))
|
|
145
|
-
|
|
146
|
-
const isEnumSchema = node.type === 'enum'
|
|
147
|
-
|
|
148
|
-
let typedName = resolveName({
|
|
149
|
-
name: node.name,
|
|
150
|
-
pluginName: plugin.name,
|
|
151
|
-
type: 'type',
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
if (['asConst', 'asPascalConst'].includes(enumType) && isEnumSchema) {
|
|
155
|
-
typedName = typedName += 'Key'
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const type = {
|
|
159
|
-
name: resolveName({
|
|
160
|
-
name: node.name,
|
|
161
|
-
pluginName: plugin.name,
|
|
162
|
-
type: 'function',
|
|
163
|
-
}),
|
|
164
|
-
typedName,
|
|
165
|
-
file: getFile({
|
|
166
|
-
name: node.name,
|
|
167
|
-
pluginName: plugin.name,
|
|
168
|
-
extname: '.ts',
|
|
169
|
-
mode,
|
|
170
|
-
// options: {
|
|
171
|
-
// group
|
|
172
|
-
// },
|
|
173
|
-
}),
|
|
174
|
-
} as const
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<File baseName={type.file.baseName} path={type.file.path} meta={type.file.meta}>
|
|
178
|
-
{mode === 'split' &&
|
|
179
|
-
imports.map((imp) => (
|
|
180
|
-
<File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={type.file.path} path={imp.path} name={imp.name} isTypeOnly />
|
|
181
|
-
))}
|
|
182
|
-
|
|
183
|
-
<Type
|
|
184
|
-
name={type.name}
|
|
185
|
-
typedName={type.typedName}
|
|
186
|
-
node={node}
|
|
187
|
-
enumType={enumType}
|
|
188
|
-
enumKeyCasing={enumKeyCasing}
|
|
189
|
-
optionalType={optionalType}
|
|
190
|
-
arrayType={arrayType}
|
|
191
|
-
syntaxType={syntaxType}
|
|
192
|
-
/>
|
|
193
|
-
</File>
|
|
194
|
-
)
|
|
195
|
-
},
|
|
196
|
-
})
|
package/src/parser.ts
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
import { jsStringEscape } from '@internals/utils'
|
|
2
|
-
import type { SchemaKeywordMapper, SchemaMapper } from '@kubb/plugin-oas'
|
|
3
|
-
import { createParser, isKeyword, schemaKeywords } from '@kubb/plugin-oas'
|
|
4
|
-
import type ts from 'typescript'
|
|
5
|
-
import * as factory from './factory.ts'
|
|
6
|
-
|
|
7
|
-
export const typeKeywordMapper = {
|
|
8
|
-
any: () => factory.keywordTypeNodes.any,
|
|
9
|
-
unknown: () => factory.keywordTypeNodes.unknown,
|
|
10
|
-
void: () => factory.keywordTypeNodes.void,
|
|
11
|
-
number: () => factory.keywordTypeNodes.number,
|
|
12
|
-
integer: () => factory.keywordTypeNodes.number,
|
|
13
|
-
bigint: () => factory.keywordTypeNodes.bigint,
|
|
14
|
-
object: (nodes?: ts.TypeElement[]) => {
|
|
15
|
-
if (!nodes || !nodes.length) {
|
|
16
|
-
return factory.keywordTypeNodes.object
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return factory.createTypeLiteralNode(nodes)
|
|
20
|
-
},
|
|
21
|
-
string: () => factory.keywordTypeNodes.string,
|
|
22
|
-
boolean: () => factory.keywordTypeNodes.boolean,
|
|
23
|
-
undefined: () => factory.keywordTypeNodes.undefined,
|
|
24
|
-
nullable: undefined,
|
|
25
|
-
null: () => factory.keywordTypeNodes.null,
|
|
26
|
-
nullish: undefined,
|
|
27
|
-
array: (nodes?: ts.TypeNode[], arrayType?: 'array' | 'generic') => {
|
|
28
|
-
if (!nodes) {
|
|
29
|
-
return undefined
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return factory.createArrayDeclaration({ nodes, arrayType })
|
|
33
|
-
},
|
|
34
|
-
tuple: (nodes?: ts.TypeNode[], rest?: ts.TypeNode, min?: number, max?: number) => {
|
|
35
|
-
if (!nodes) {
|
|
36
|
-
return undefined
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (max) {
|
|
40
|
-
nodes = nodes.slice(0, max)
|
|
41
|
-
|
|
42
|
-
if (nodes.length < max && rest) {
|
|
43
|
-
nodes = [...nodes, ...Array(max - nodes.length).fill(rest)]
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (min) {
|
|
48
|
-
nodes = nodes.map((node, index) => (index >= min ? factory.createOptionalTypeNode(node) : node))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (typeof max === 'undefined' && rest) {
|
|
52
|
-
nodes.push(factory.createRestTypeNode(factory.createArrayTypeNode(rest)))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return factory.createTupleTypeNode(nodes)
|
|
56
|
-
},
|
|
57
|
-
enum: (name?: string) => {
|
|
58
|
-
if (!name) {
|
|
59
|
-
return undefined
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return factory.createTypeReferenceNode(name, undefined)
|
|
63
|
-
},
|
|
64
|
-
union: (nodes?: ts.TypeNode[]) => {
|
|
65
|
-
if (!nodes) {
|
|
66
|
-
return undefined
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return factory.createUnionDeclaration({
|
|
70
|
-
withParentheses: true,
|
|
71
|
-
nodes,
|
|
72
|
-
})
|
|
73
|
-
},
|
|
74
|
-
const: (name?: string | number | boolean, format?: 'string' | 'number' | 'boolean') => {
|
|
75
|
-
if (name === null || name === undefined || name === '') {
|
|
76
|
-
return undefined
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (format === 'boolean') {
|
|
80
|
-
if (name === true) {
|
|
81
|
-
return factory.createLiteralTypeNode(factory.createTrue())
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return factory.createLiteralTypeNode(factory.createFalse())
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (format === 'number' && typeof name === 'number') {
|
|
88
|
-
return factory.createLiteralTypeNode(factory.createNumericLiteral(name))
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return factory.createLiteralTypeNode(factory.createStringLiteral(name.toString()))
|
|
92
|
-
},
|
|
93
|
-
datetime: () => factory.keywordTypeNodes.string,
|
|
94
|
-
date: (type: 'date' | 'string' = 'string') =>
|
|
95
|
-
type === 'string' ? factory.keywordTypeNodes.string : factory.createTypeReferenceNode(factory.createIdentifier('Date')),
|
|
96
|
-
time: (type: 'date' | 'string' = 'string') =>
|
|
97
|
-
type === 'string' ? factory.keywordTypeNodes.string : factory.createTypeReferenceNode(factory.createIdentifier('Date')),
|
|
98
|
-
uuid: () => factory.keywordTypeNodes.string,
|
|
99
|
-
url: () => factory.keywordTypeNodes.string,
|
|
100
|
-
default: undefined,
|
|
101
|
-
and: (nodes?: ts.TypeNode[]) => {
|
|
102
|
-
if (!nodes) {
|
|
103
|
-
return undefined
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return factory.createIntersectionDeclaration({
|
|
107
|
-
withParentheses: true,
|
|
108
|
-
nodes,
|
|
109
|
-
})
|
|
110
|
-
},
|
|
111
|
-
describe: undefined,
|
|
112
|
-
min: undefined,
|
|
113
|
-
max: undefined,
|
|
114
|
-
optional: undefined,
|
|
115
|
-
matches: () => factory.keywordTypeNodes.string,
|
|
116
|
-
email: () => factory.keywordTypeNodes.string,
|
|
117
|
-
firstName: undefined,
|
|
118
|
-
lastName: undefined,
|
|
119
|
-
password: undefined,
|
|
120
|
-
phone: undefined,
|
|
121
|
-
readOnly: undefined,
|
|
122
|
-
writeOnly: undefined,
|
|
123
|
-
ref: (propertyName?: string) => {
|
|
124
|
-
if (!propertyName) {
|
|
125
|
-
return undefined
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return factory.createTypeReferenceNode(propertyName, undefined)
|
|
129
|
-
},
|
|
130
|
-
blob: () => factory.createTypeReferenceNode('Blob', []),
|
|
131
|
-
deprecated: undefined,
|
|
132
|
-
example: undefined,
|
|
133
|
-
schema: undefined,
|
|
134
|
-
catchall: undefined,
|
|
135
|
-
name: undefined,
|
|
136
|
-
interface: undefined,
|
|
137
|
-
exclusiveMaximum: undefined,
|
|
138
|
-
exclusiveMinimum: undefined,
|
|
139
|
-
} satisfies SchemaMapper<ts.TypeNode | null | undefined>
|
|
140
|
-
|
|
141
|
-
type ParserOptions = {
|
|
142
|
-
/**
|
|
143
|
-
* @default `'questionToken'`
|
|
144
|
-
*/
|
|
145
|
-
optionalType: 'questionToken' | 'undefined' | 'questionTokenAndUndefined'
|
|
146
|
-
/**
|
|
147
|
-
* @default `'array'`
|
|
148
|
-
*/
|
|
149
|
-
arrayType: 'array' | 'generic'
|
|
150
|
-
/**
|
|
151
|
-
* Choose to use `enum`, `asConst`, `asPascalConst`, `constEnum`, `literal`, or `inlineLiteral` for enums.
|
|
152
|
-
* - `enum`: TypeScript enum
|
|
153
|
-
* - `asConst`: const with camelCase name (e.g., `petType`)
|
|
154
|
-
* - `asPascalConst`: const with PascalCase name (e.g., `PetType`)
|
|
155
|
-
* - `constEnum`: const enum
|
|
156
|
-
* - `literal`: literal union type
|
|
157
|
-
* - `inlineLiteral`: inline enum values directly into the type (default in v5)
|
|
158
|
-
* @default `'asConst'`
|
|
159
|
-
* @note In Kubb v5, `inlineLiteral` becomes the default.
|
|
160
|
-
*/
|
|
161
|
-
enumType: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal' | 'inlineLiteral'
|
|
162
|
-
mapper?: Record<string, ts.PropertySignature>
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Recursively parses a schema tree node into a corresponding TypeScript AST node.
|
|
167
|
-
*
|
|
168
|
-
* Maps OpenAPI schema keywords to TypeScript AST nodes using the `typeKeywordMapper`, handling complex types such as unions, intersections, arrays, tuples (with optional/rest elements and length constraints), enums, constants, references, and objects with property modifiers and documentation annotations.
|
|
169
|
-
*
|
|
170
|
-
* @param current - The schema node to parse.
|
|
171
|
-
* @param siblings - Sibling schema nodes, used for context in certain mappings.
|
|
172
|
-
* @param name - The name of the schema or property being parsed.
|
|
173
|
-
* @param options - Parsing options controlling output style, property handling, and custom mappers.
|
|
174
|
-
* @returns The generated TypeScript AST node, or `undefined` if the schema keyword is not mapped.
|
|
175
|
-
*/
|
|
176
|
-
export const parse = createParser<ts.Node | null, ParserOptions>({
|
|
177
|
-
mapper: typeKeywordMapper,
|
|
178
|
-
handlers: {
|
|
179
|
-
union(tree, options) {
|
|
180
|
-
const { current, schema, name } = tree
|
|
181
|
-
|
|
182
|
-
return typeKeywordMapper.union(
|
|
183
|
-
current.args.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
|
|
184
|
-
)
|
|
185
|
-
},
|
|
186
|
-
and(tree, options) {
|
|
187
|
-
const { current, schema, name } = tree
|
|
188
|
-
|
|
189
|
-
return typeKeywordMapper.and(
|
|
190
|
-
current.args.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
|
|
191
|
-
)
|
|
192
|
-
},
|
|
193
|
-
array(tree, options) {
|
|
194
|
-
const { current, schema, name } = tree
|
|
195
|
-
|
|
196
|
-
return typeKeywordMapper.array(
|
|
197
|
-
current.args.items.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
|
|
198
|
-
options.arrayType,
|
|
199
|
-
)
|
|
200
|
-
},
|
|
201
|
-
enum(tree, options) {
|
|
202
|
-
const { current } = tree
|
|
203
|
-
|
|
204
|
-
// If enumType is 'inlineLiteral', generate the literal union inline instead of a type reference
|
|
205
|
-
if (options.enumType === 'inlineLiteral') {
|
|
206
|
-
const enumValues = current.args.items
|
|
207
|
-
.map((item) => item.value)
|
|
208
|
-
.filter((value): value is string | number | boolean => value !== undefined && value !== null)
|
|
209
|
-
.map((value) => {
|
|
210
|
-
const format = typeof value === 'number' ? 'number' : typeof value === 'boolean' ? 'boolean' : 'string'
|
|
211
|
-
return typeKeywordMapper.const(value, format)
|
|
212
|
-
})
|
|
213
|
-
.filter(Boolean) as ts.TypeNode[]
|
|
214
|
-
|
|
215
|
-
return typeKeywordMapper.union(enumValues)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Adding suffix to enum (see https://github.com/kubb-labs/kubb/issues/1873)
|
|
219
|
-
return typeKeywordMapper.enum(['asConst', 'asPascalConst'].includes(options.enumType) ? `${current.args.typeName}Key` : current.args.typeName)
|
|
220
|
-
},
|
|
221
|
-
ref(tree, _options) {
|
|
222
|
-
const { current } = tree
|
|
223
|
-
|
|
224
|
-
return typeKeywordMapper.ref(current.args.name)
|
|
225
|
-
},
|
|
226
|
-
blob() {
|
|
227
|
-
return typeKeywordMapper.blob()
|
|
228
|
-
},
|
|
229
|
-
tuple(tree, options) {
|
|
230
|
-
const { current, schema, name } = tree
|
|
231
|
-
|
|
232
|
-
return typeKeywordMapper.tuple(
|
|
233
|
-
current.args.items.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
|
|
234
|
-
current.args.rest &&
|
|
235
|
-
((this.parse({ schema, parent: current, name, current: current.args.rest, siblings: [] }, options) ?? undefined) as ts.TypeNode | undefined),
|
|
236
|
-
current.args.min,
|
|
237
|
-
current.args.max,
|
|
238
|
-
)
|
|
239
|
-
},
|
|
240
|
-
const(tree, _options) {
|
|
241
|
-
const { current } = tree
|
|
242
|
-
|
|
243
|
-
return typeKeywordMapper.const(current.args.name, current.args.format)
|
|
244
|
-
},
|
|
245
|
-
object(tree, options) {
|
|
246
|
-
const { current, schema, name } = tree
|
|
247
|
-
|
|
248
|
-
const properties = Object.entries(current.args?.properties || {})
|
|
249
|
-
.filter((item) => {
|
|
250
|
-
const schemas = item[1]
|
|
251
|
-
return schemas && typeof schemas.map === 'function'
|
|
252
|
-
})
|
|
253
|
-
.map(([name, schemas]) => {
|
|
254
|
-
const nameSchema = schemas.find((schema) => schema.keyword === schemaKeywords.name) as SchemaKeywordMapper['name']
|
|
255
|
-
const mappedName = nameSchema?.args || name
|
|
256
|
-
|
|
257
|
-
// custom mapper(pluginOptions)
|
|
258
|
-
// Use Object.hasOwn to avoid matching inherited properties like 'toString', 'valueOf', etc.
|
|
259
|
-
if (options.mapper && Object.hasOwn(options.mapper, mappedName)) {
|
|
260
|
-
return options.mapper[mappedName]
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const isNullish = schemas.some((schema) => schema.keyword === schemaKeywords.nullish)
|
|
264
|
-
const isNullable = schemas.some((schema) => schema.keyword === schemaKeywords.nullable)
|
|
265
|
-
const isOptional = schemas.some((schema) => schema.keyword === schemaKeywords.optional)
|
|
266
|
-
const isReadonly = schemas.some((schema) => schema.keyword === schemaKeywords.readOnly)
|
|
267
|
-
const describeSchema = schemas.find((schema) => schema.keyword === schemaKeywords.describe) as SchemaKeywordMapper['describe'] | undefined
|
|
268
|
-
const deprecatedSchema = schemas.find((schema) => schema.keyword === schemaKeywords.deprecated) as SchemaKeywordMapper['deprecated'] | undefined
|
|
269
|
-
const defaultSchema = schemas.find((schema) => schema.keyword === schemaKeywords.default) as SchemaKeywordMapper['default'] | undefined
|
|
270
|
-
const exampleSchema = schemas.find((schema) => schema.keyword === schemaKeywords.example) as SchemaKeywordMapper['example'] | undefined
|
|
271
|
-
const schemaSchema = schemas.find((schema) => schema.keyword === schemaKeywords.schema) as SchemaKeywordMapper['schema'] | undefined
|
|
272
|
-
const minSchema = schemas.find((schema) => schema.keyword === schemaKeywords.min) as SchemaKeywordMapper['min'] | undefined
|
|
273
|
-
const maxSchema = schemas.find((schema) => schema.keyword === schemaKeywords.max) as SchemaKeywordMapper['max'] | undefined
|
|
274
|
-
const matchesSchema = schemas.find((schema) => schema.keyword === schemaKeywords.matches) as SchemaKeywordMapper['matches'] | undefined
|
|
275
|
-
|
|
276
|
-
let type = schemas
|
|
277
|
-
.map((it) =>
|
|
278
|
-
this.parse(
|
|
279
|
-
{
|
|
280
|
-
schema,
|
|
281
|
-
parent: current,
|
|
282
|
-
name,
|
|
283
|
-
current: it,
|
|
284
|
-
siblings: schemas,
|
|
285
|
-
},
|
|
286
|
-
options,
|
|
287
|
-
),
|
|
288
|
-
)
|
|
289
|
-
.filter(Boolean)[0] as ts.TypeNode
|
|
290
|
-
|
|
291
|
-
if (isNullable) {
|
|
292
|
-
type = factory.createUnionDeclaration({
|
|
293
|
-
nodes: [type, factory.keywordTypeNodes.null],
|
|
294
|
-
}) as ts.TypeNode
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (isNullish && ['undefined', 'questionTokenAndUndefined'].includes(options.optionalType as string)) {
|
|
298
|
-
type = factory.createUnionDeclaration({
|
|
299
|
-
nodes: [type, factory.keywordTypeNodes.undefined],
|
|
300
|
-
}) as ts.TypeNode
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(options.optionalType as string)) {
|
|
304
|
-
type = factory.createUnionDeclaration({
|
|
305
|
-
nodes: [type, factory.keywordTypeNodes.undefined],
|
|
306
|
-
}) as ts.TypeNode
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const propertyNode = factory.createPropertySignature({
|
|
310
|
-
questionToken: isOptional || isNullish ? ['questionToken', 'questionTokenAndUndefined'].includes(options.optionalType as string) : false,
|
|
311
|
-
name: mappedName,
|
|
312
|
-
type,
|
|
313
|
-
readOnly: isReadonly,
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
return factory.appendJSDocToNode({
|
|
317
|
-
node: propertyNode,
|
|
318
|
-
comments: [
|
|
319
|
-
describeSchema ? `@description ${jsStringEscape(describeSchema.args)}` : undefined,
|
|
320
|
-
deprecatedSchema ? '@deprecated' : undefined,
|
|
321
|
-
minSchema ? `@minLength ${minSchema.args}` : undefined,
|
|
322
|
-
maxSchema ? `@maxLength ${maxSchema.args}` : undefined,
|
|
323
|
-
matchesSchema ? `@pattern ${matchesSchema.args}` : undefined,
|
|
324
|
-
defaultSchema ? `@default ${defaultSchema.args}` : undefined,
|
|
325
|
-
exampleSchema ? `@example ${exampleSchema.args}` : undefined,
|
|
326
|
-
schemaSchema?.args?.type || schemaSchema?.args?.format
|
|
327
|
-
? [`@type ${schemaSchema?.args?.type || 'unknown'}${!isOptional ? '' : ' | undefined'}`, schemaSchema?.args?.format].filter(Boolean).join(', ')
|
|
328
|
-
: undefined,
|
|
329
|
-
].filter(Boolean),
|
|
330
|
-
})
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
let additionalProperties: any
|
|
334
|
-
|
|
335
|
-
if (current.args?.additionalProperties?.length) {
|
|
336
|
-
let additionalPropertiesType = current.args.additionalProperties
|
|
337
|
-
.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options))
|
|
338
|
-
.filter(Boolean)
|
|
339
|
-
.at(0) as ts.TypeNode
|
|
340
|
-
|
|
341
|
-
const isNullable = current.args?.additionalProperties.some((schema) => isKeyword(schema, schemaKeywords.nullable))
|
|
342
|
-
if (isNullable) {
|
|
343
|
-
additionalPropertiesType = factory.createUnionDeclaration({
|
|
344
|
-
nodes: [additionalPropertiesType, factory.keywordTypeNodes.null],
|
|
345
|
-
}) as ts.TypeNode
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// When there are typed properties alongside additionalProperties, use 'unknown' type
|
|
349
|
-
// for the index signature to avoid TS2411 errors (index signature type conflicts with property types).
|
|
350
|
-
// This occurs commonly in QueryParams where some params are typed (enums, objects) and
|
|
351
|
-
// others are dynamic (additionalProperties with explode=true).
|
|
352
|
-
const hasTypedProperties = properties.length > 0
|
|
353
|
-
const indexSignatureType = hasTypedProperties ? factory.keywordTypeNodes.unknown : additionalPropertiesType
|
|
354
|
-
|
|
355
|
-
additionalProperties = factory.createIndexSignature(indexSignatureType)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
let patternProperties: ts.TypeNode | ts.IndexSignatureDeclaration | undefined
|
|
359
|
-
|
|
360
|
-
if (current.args?.patternProperties) {
|
|
361
|
-
const allPatternSchemas = Object.values(current.args.patternProperties).flat()
|
|
362
|
-
|
|
363
|
-
if (allPatternSchemas.length > 0) {
|
|
364
|
-
patternProperties = allPatternSchemas
|
|
365
|
-
.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options))
|
|
366
|
-
.filter(Boolean)
|
|
367
|
-
.at(0) as ts.TypeNode
|
|
368
|
-
|
|
369
|
-
const isNullable = allPatternSchemas.some((schema) => isKeyword(schema, schemaKeywords.nullable))
|
|
370
|
-
if (isNullable) {
|
|
371
|
-
patternProperties = factory.createUnionDeclaration({
|
|
372
|
-
nodes: [patternProperties, factory.keywordTypeNodes.null],
|
|
373
|
-
}) as ts.TypeNode
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
patternProperties = factory.createIndexSignature(patternProperties)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return typeKeywordMapper.object([...properties, additionalProperties, patternProperties].filter(Boolean))
|
|
381
|
-
},
|
|
382
|
-
datetime() {
|
|
383
|
-
return typeKeywordMapper.datetime()
|
|
384
|
-
},
|
|
385
|
-
date(tree) {
|
|
386
|
-
const { current } = tree
|
|
387
|
-
|
|
388
|
-
return typeKeywordMapper.date(current.args.type)
|
|
389
|
-
},
|
|
390
|
-
time(tree) {
|
|
391
|
-
const { current } = tree
|
|
392
|
-
|
|
393
|
-
return typeKeywordMapper.time(current.args.type)
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
})
|